// This file is part of OpenTSDB.
// Copyright (C) 2010-2012 The OpenTSDB Authors.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 2.1 of the License, or (at your
// option) any later version. This program is distributed in the hope that it
// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
// General Public License for more details. You should have received a copy
// of the GNU Lesser General Public License along with this program. If not,
// see <http://www.gnu.org/licenses/>.
package net.opentsdb.tools;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.opentsdb.core.Aggregator;
import net.opentsdb.core.Aggregators;
import net.opentsdb.core.Query;
import net.opentsdb.core.DataPoint;
import net.opentsdb.core.DataPoints;
import net.opentsdb.core.RateOptions;
import net.opentsdb.core.Tags;
import net.opentsdb.core.TSDB;
import net.opentsdb.graph.Plot;
import net.opentsdb.utils.Config;
import net.opentsdb.utils.DateTime;
final class CliQuery {
private static final Logger LOG = LoggerFactory.getLogger(CliQuery.class);
/** Prints usage and exits with the given retval. */
private static void usage(final ArgP argp, final String errmsg,
final int retval) {
System.err.println(errmsg);
System.err.println("Usage: query"
+ " [Gnuplot opts] START-DATE [END-DATE] <query> [queries...]\n"
+ "A query has the form:\n"
+ " FUNC [rate] [counter,max,reset] [downsample N FUNC] SERIES [TAGS]\n"
+ "For example:\n"
+ " 2010/03/11-20:57 sum my.awsum.metric host=blah"
+ " sum some.other.metric host=blah state=foo\n"
+ "Dates must follow this format: YYYY/MM/DD-HH:MM[:SS] or Unix Epoch\n"
+ " or relative time such as 1y-ago, 2d-ago, etc.\n"
+ "Supported values for FUNC: " + Aggregators.set()
+ "\nGnuplot options are of the form: +option=value");
if (argp != null) {
System.err.print(argp.usage());
}
System.exit(retval);
}
public static void main(String[] args) throws Exception {
ArgP argp = new ArgP();
CliOptions.addCommon(argp);
CliOptions.addVerbose(argp);
argp.addOption("--graph", "BASEPATH",
"Output data points to a set of files for gnuplot."
+ " The path of the output files will start with"
+ " BASEPATH.");
args = CliOptions.parse(argp, args);
if (args == null) {
usage(argp, "Invalid usage.", 1);
} else if (args.length < 3) {
usage(argp, "Not enough arguments.", 2);
}
// get a config object
Config config = CliOptions.getConfig(argp);
final TSDB tsdb = new TSDB(config);
tsdb.checkNecessaryTablesExist().joinUninterruptibly();
final String basepath = argp.get("--graph");
argp = null;
Plot plot = null;
try {
plot = doQuery(tsdb, args, basepath != null);
} finally {
try {
tsdb.shutdown().joinUninterruptibly();
} catch (Exception e) {
LOG.error("Unexpected exception", e);
System.exit(1);
}
}
if (plot != null) {
try {
final int npoints = plot.dumpToFiles(basepath);
LOG.info("Wrote " + npoints + " for Gnuplot");
} catch (IOException e) {
LOG.error("Failed to write the Gnuplot file under " + basepath, e);
System.exit(1);
}
}
}
private static Plot doQuery(final TSDB tsdb,
final String args[],
final boolean want_plot) {
final ArrayList<String> plotparams = new ArrayList<String>();
final ArrayList<Query> queries = new ArrayList<Query>();
final ArrayList<String> plotoptions = new ArrayList<String>();
parseCommandLineQuery(args, tsdb, queries, plotparams, plotoptions);
if (queries.isEmpty()) {
usage(null, "Not enough arguments, need at least one query.", 2);
}
final Plot plot = (want_plot ? new Plot(queries.get(0).getStartTime(),
queries.get(0).getEndTime())
: null);
if (want_plot) {
plot.setParams(parsePlotParams(plotparams));
}
final int nqueries = queries.size();
for (int i = 0; i < nqueries; i++) {
// TODO(tsuna): Optimization: run each query in parallel.
final StringBuilder buf = want_plot ? null : new StringBuilder();
for (final DataPoints datapoints : queries.get(i).run()) {
if (want_plot) {
plot.add(datapoints, plotoptions.get(i));
} else {
final String metric = datapoints.metricName();
final String tagz = datapoints.getTags().toString();
for (final DataPoint datapoint : datapoints) {
buf.append(metric)
.append(' ')
.append(datapoint.timestamp())
.append(' ');
if (datapoint.isInteger()) {
buf.append(datapoint.longValue());
} else {
buf.append(String.format("%f", datapoint.doubleValue()));
}
buf.append(' ').append(tagz).append('\n');
System.out.print(buf);
buf.delete(0, buf.length());
}
}
}
}
return plot;
}
/**
* Parses the query from the command lines.
* @param args The command line arguments.
* @param tsdb The TSDB to use.
* @param queries The list in which {@link Query}s will be appended.
* @param plotparams The list in which global plot parameters will be
* appended. Ignored if {@code null}.
* @param plotoptions The list in which per-line plot options will be
* appended. Ignored if {@code null}.
*/
static void parseCommandLineQuery(final String[] args,
final TSDB tsdb,
final ArrayList<Query> queries,
final ArrayList<String> plotparams,
final ArrayList<String> plotoptions) {
long start_ts = DateTime.parseDateTimeString(args[0], null);
if (start_ts >= 0)
start_ts /= 1000;
long end_ts = -1;
if (args.length > 3){
// see if we can detect an end time
try{
if (args[1].charAt(0) != '+'
&& (args[1].indexOf(':') >= 0
|| args[1].indexOf('/') >= 0
|| args[1].indexOf('-') >= 0
|| Long.parseLong(args[1]) > 0)){
end_ts = DateTime.parseDateTimeString(args[1], null);
}
}catch (NumberFormatException nfe) {
// ignore it as it means the third parameter is likely the aggregator
}
}
// temp fixup to seconds from ms until the rest of TSDB supports ms
// Note you can't append this to the DateTime.parseDateTimeString() call as
// it clobbers -1 results
if (end_ts >= 0)
end_ts /= 1000;
int i = end_ts < 0 ? 1 : 2;
while (i < args.length && args[i].charAt(0) == '+') {
if (plotparams != null) {
plotparams.add(args[i]);
}
i++;
}
while (i < args.length) {
final Aggregator agg = Aggregators.get(args[i++]);
final boolean rate = args[i].equals("rate");
RateOptions rate_options = new RateOptions(false, Long.MAX_VALUE,
RateOptions.DEFAULT_RESET_VALUE);
if (rate) {
i++;
long counterMax = Long.MAX_VALUE;
long resetValue = RateOptions.DEFAULT_RESET_VALUE;
if (args[i].startsWith("counter")) {
String[] parts = Tags.splitString(args[i], ',');
if (parts.length >= 2 && parts[1].length() > 0) {
counterMax = Long.parseLong(parts[1]);
}
if (parts.length >= 3 && parts[2].length() > 0) {
resetValue = Long.parseLong(parts[2]);
}
rate_options = new RateOptions(true, counterMax, resetValue);
i++;
}
}
final boolean downsample = args[i].equals("downsample");
if (downsample) {
i++;
}
final long interval = downsample ? Long.parseLong(args[i++]) : 0;
final Aggregator sampler = downsample ? Aggregators.get(args[i++]) : null;
final String metric = args[i++];
final HashMap<String, String> tags = new HashMap<String, String>();
while (i < args.length && args[i].indexOf(' ', 1) < 0
&& args[i].indexOf('=', 1) > 0) {
Tags.parse(tags, args[i++]);
}
if (i < args.length && args[i].indexOf(' ', 1) > 0) {
plotoptions.add(args[i++]);
}
final Query query = tsdb.newQuery();
query.setStartTime(start_ts);
if (end_ts > 0) {
query.setEndTime(end_ts);
}
query.setTimeSeries(metric, tags, agg, rate, rate_options);
if (downsample) {
query.downsample(interval, sampler);
}
queries.add(query);
}
}
private static HashMap<String, String> parsePlotParams(final ArrayList<String> params) {
final HashMap<String, String> result =
new HashMap<String, String>(params.size());
for (final String param : params) {
Tags.parse(result, param.substring(1));
}
return result;
}
}