'id', COMPRESSION => '$COMPRESSION'},
36 | {NAME => 'name', COMPRESSION => '$COMPRESSION'}
37 |
38 | create '$TSDB_TABLE',
39 | {NAME => 't', VERSIONS => 1, COMPRESSION => '$COMPRESSION', BLOOMFILTER => '$BLOOMFILTER'}
40 | EOF
41 |
--------------------------------------------------------------------------------
/test/uid/TestNoSuchUniqueId.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2010-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.uid;
14 |
15 | import junit.framework.TestCase;
16 |
17 | public final class TestNoSuchUniqueId extends TestCase {
18 |
19 | public void testMessage() {
20 | final byte[] id = { 0, 'A', 'a' };
21 | final NoSuchUniqueId e = new NoSuchUniqueId("Foo", id);
22 | assertEquals("No such unique ID for 'Foo': [0, 65, 97]", e.getMessage());
23 | }
24 |
25 | public void testFields() {
26 | final String kind = "bar";
27 | final byte[] id = { 42, '!' };
28 | final NoSuchUniqueId e = new NoSuchUniqueId(kind, id);
29 | assertEquals(kind, e.kind());
30 | assertEquals(id, e.id());
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/core/RowKey.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2010-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.core;
14 |
15 | import java.util.Arrays;
16 |
17 | /** Helper functions to deal with the row key. */
18 | final class RowKey {
19 |
20 | private RowKey() {
21 | // Can't create instances of this utility class.
22 | }
23 |
24 | /**
25 | * Extracts the name of the metric ID contained in a row key.
26 | * @param tsdb The TSDB to use.
27 | * @param row The actual row key.
28 | * @return The name of the metric.
29 | */
30 | static String metricName(final TSDB tsdb, final byte[] row) {
31 | final byte[] id = Arrays.copyOfRange(row, 0, tsdb.metrics.width());
32 | return tsdb.metrics.getName(id);
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/core/IllegalDataException.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2011-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.core;
14 |
15 | /**
16 | * Some illegal / malformed / corrupted data has been found in HBase.
17 | */
18 | public final class IllegalDataException extends IllegalStateException {
19 |
20 | /**
21 | * Constructor.
22 | *
23 | * @param msg Message describing the problem.
24 | */
25 | public IllegalDataException(final String msg) {
26 | super(msg);
27 | }
28 |
29 | /**
30 | * Constructor.
31 | *
32 | * @param msg Message describing the problem.
33 | */
34 | public IllegalDataException(final String msg, final Throwable cause) {
35 | super(msg, cause);
36 | }
37 |
38 | static final long serialVersionUID = 1307719142;
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/third_party/slf4j/include.mk:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2011-2013 The OpenTSDB Authors.
2 | #
3 | # This library is free software: you can redistribute it and/or modify it
4 | # under the terms of the GNU Lesser General Public License as published
5 | # by the Free Software Foundation, either version 2.1 of the License, or
6 | # (at your option) any later version.
7 | #
8 | # This library is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU Lesser General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU Lesser General Public License
14 | # along with this library. If not, see .
15 |
16 | SLF4J_VERSION = 1.7.2
17 |
18 |
19 | LOG4J_OVER_SLF4J_VERSION := $(SLF4J_VERSION)
20 | LOG4J_OVER_SLF4J := third_party/slf4j/log4j-over-slf4j-$(LOG4J_OVER_SLF4J_VERSION).jar
21 | LOG4J_OVER_SLF4J_BASE_URL := $(OPENTSDB_THIRD_PARTY_BASE_URL)
22 |
23 | $(LOG4J_OVER_SLF4J): $(LOG4J_OVER_SLF4J).md5
24 | set dummy "$(LOG4J_OVER_SLF4J_BASE_URL)" "$(LOG4J_OVER_SLF4J)"; shift; $(FETCH_DEPENDENCY)
25 |
26 |
27 | SLF4J_API_VERSION := $(SLF4J_VERSION)
28 | SLF4J_API := third_party/slf4j/slf4j-api-$(SLF4J_API_VERSION).jar
29 | SLF4J_API_BASE_URL := $(OPENTSDB_THIRD_PARTY_BASE_URL)
30 |
31 | $(SLF4J_API): $(SLF4J_API).md5
32 | set dummy "$(SLF4J_API_BASE_URL)" "$(SLF4J_API)"; shift; $(FETCH_DEPENDENCY)
33 |
34 | THIRD_PARTY += $(LOG4J_OVER_SLF4J) $(SLF4J_API)
35 |
--------------------------------------------------------------------------------
/third_party/logback/include.mk:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2011-2013 The OpenTSDB Authors.
2 | #
3 | # This library is free software: you can redistribute it and/or modify it
4 | # under the terms of the GNU Lesser General Public License as published
5 | # by the Free Software Foundation, either version 2.1 of the License, or
6 | # (at your option) any later version.
7 | #
8 | # This library is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU Lesser General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU Lesser General Public License
14 | # along with this library. If not, see .
15 |
16 | LOGBACK_VERSION = 1.0.9
17 |
18 | LOGBACK_CLASSIC_VERSION := $(LOGBACK_VERSION)
19 | LOGBACK_CLASSIC := third_party/logback/logback-classic-$(LOGBACK_CLASSIC_VERSION).jar
20 | LOGBACK_CLASSIC_BASE_URL := $(OPENTSDB_THIRD_PARTY_BASE_URL)
21 |
22 | $(LOGBACK_CLASSIC): $(LOGBACK_CLASSIC).md5
23 | set dummy "$(LOGBACK_CLASSIC_BASE_URL)" "$(LOGBACK_CLASSIC)"; shift; $(FETCH_DEPENDENCY)
24 |
25 |
26 | LOGBACK_CORE_VERSION := $(LOGBACK_VERSION)
27 | LOGBACK_CORE := third_party/logback/logback-core-$(LOGBACK_CORE_VERSION).jar
28 | LOGBACK_CORE_BASE_URL := $(OPENTSDB_THIRD_PARTY_BASE_URL)
29 |
30 | $(LOGBACK_CORE): $(LOGBACK_CORE).md5
31 | set dummy "$(LOGBACK_CORE_BASE_URL)" "$(LOGBACK_CORE)"; shift; $(FETCH_DEPENDENCY)
32 |
33 | THIRD_PARTY += $(LOGBACK_CLASSIC) $(LOGBACK_CORE)
34 |
--------------------------------------------------------------------------------
/third_party/include.mk:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2011-2013 The OpenTSDB Authors.
2 | #
3 | # This library is free software: you can redistribute it and/or modify it
4 | # under the terms of the GNU Lesser General Public License as published
5 | # by the Free Software Foundation, either version 2.1 of the License, or
6 | # (at your option) any later version.
7 | #
8 | # This library is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU Lesser General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU Lesser General Public License
14 | # along with this library. If not, see .
15 |
16 | OPENTSDB_THIRD_PARTY_BASE_URL := http://opentsdb.googlecode.com/files
17 | FETCH_DEPENDENCY := ./build-aux/fetchdep.sh "$$@"
18 | all-am: build-aux/fetchdep.sh
19 | THIRD_PARTY =
20 |
21 | include third_party/guava/include.mk
22 | include third_party/gwt/include.mk
23 | include third_party/hamcrest/include.mk
24 | include third_party/hbase/include.mk
25 | include third_party/javassist/include.mk
26 | include third_party/junit/include.mk
27 | include third_party/logback/include.mk
28 | include third_party/mockito/include.mk
29 | include third_party/netty/include.mk
30 | include third_party/objenesis/include.mk
31 | include third_party/powermock/include.mk
32 | include third_party/protobuf/include.mk
33 | include third_party/slf4j/include.mk
34 | include third_party/suasync/include.mk
35 | include third_party/zookeeper/include.mk
36 |
--------------------------------------------------------------------------------
/src/tsd/WordSplitter.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2010-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.tsd;
14 |
15 | import java.nio.charset.Charset;
16 |
17 | import org.jboss.netty.buffer.ChannelBuffer;
18 | import org.jboss.netty.channel.Channel;
19 | import org.jboss.netty.channel.ChannelHandlerContext;
20 | import org.jboss.netty.handler.codec.oneone.OneToOneDecoder;
21 |
22 | import net.opentsdb.core.Tags;
23 |
24 | /**
25 | * Splits a ChannelBuffer in multiple space separated words.
26 | */
27 | final class WordSplitter extends OneToOneDecoder {
28 |
29 | private static final Charset CHARSET = Charset.forName("ISO-8859-1");
30 |
31 | /** Constructor. */
32 | public WordSplitter() {
33 | }
34 |
35 | @Override
36 | protected Object decode(final ChannelHandlerContext ctx,
37 | final Channel channel,
38 | final Object msg) throws Exception {
39 | return Tags.splitString(((ChannelBuffer) msg).toString(CHARSET), ' ');
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/configure.ac:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2011-2012 The OpenTSDB Authors.
2 | #
3 | # This library is free software: you can redistribute it and/or modify it
4 | # under the terms of the GNU Lesser General Public License as published
5 | # by the Free Software Foundation, either version 2.1 of the License, or
6 | # (at your option) any later version.
7 | #
8 | # This library is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU Lesser General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU Lesser General Public License
14 | # along with this library. If not, see .
15 |
16 | # Semantic Versioning (see http://semver.org/).
17 | AC_INIT([opentsdb], [1.1.0], [opentsdb@googlegroups.com])
18 | AC_CONFIG_AUX_DIR([build-aux])
19 | AM_INIT_AUTOMAKE([foreign])
20 |
21 | AC_CONFIG_FILES([
22 | Makefile
23 | ])
24 | AC_CONFIG_FILES([opentsdb.spec])
25 | AC_CONFIG_FILES([build-aux/fetchdep.sh], [chmod +x build-aux/fetchdep.sh])
26 |
27 | TSDB_FIND_PROG([md5], [md5sum md5 gmd5sum digest])
28 | if test x`basename "$MD5"` = x'digest'; then
29 | MD5='digest -a md5'
30 | fi
31 | TSDB_FIND_PROG([java])
32 | TSDB_FIND_PROG([javac])
33 | TSDB_FIND_PROG([jar])
34 | TSDB_FIND_PROG([gnuplot])
35 | AC_PATH_PROG([JAVADOC], [javadoc], [])
36 | AM_MISSING_PROG([JAVADOC], [javadoc])
37 |
38 | # Find a tool to download 3rd party dependencies.
39 | AC_PATH_PROG([WGET], [wget])
40 | AC_PATH_PROG([CURL], [curl])
41 | # Make sure we have at least one.
42 | if test -z "$WGET$CURL"; then
43 | AC_MSG_ERROR([cannot find a tool to download an URL])
44 | fi
45 |
46 | AC_SUBST([staticdir], ['${pkgdatadir}/static'])
47 |
48 | AC_OUTPUT
49 |
--------------------------------------------------------------------------------
/src/uid/NoSuchUniqueId.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2010-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.uid;
14 |
15 | import java.util.Arrays;
16 | import java.util.NoSuchElementException;
17 |
18 | /**
19 | * Exception used when a Unique ID can't be found.
20 | *
21 | * @see UniqueIdInterface
22 | */
23 | public final class NoSuchUniqueId extends NoSuchElementException {
24 |
25 | /** The 'kind' of the table. */
26 | private final String kind;
27 | /** The ID that couldn't be found. */
28 | private final byte[] id;
29 |
30 | /**
31 | * Constructor.
32 | *
33 | * @param kind The kind of unique ID that triggered the exception.
34 | * @param id The ID that couldn't be found.
35 | */
36 | public NoSuchUniqueId(final String kind, final byte[] id) {
37 | super("No such unique ID for '" + kind + "': " + Arrays.toString(id));
38 | this.kind = kind;
39 | this.id = id;
40 | }
41 |
42 | /** Returns the kind of unique ID that couldn't be found. */
43 | public String kind() {
44 | return kind;
45 | }
46 |
47 | /** Returns the unique ID that couldn't be found. */
48 | public byte[] id() {
49 | return id;
50 | }
51 |
52 | static final long serialVersionUID = 1266815251;
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/src/uid/NoSuchUniqueName.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2010-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.uid;
14 |
15 | import java.util.NoSuchElementException;
16 |
17 | /**
18 | * Exception used when a name's Unique ID can't be found.
19 | *
20 | * @see UniqueIdInterface
21 | */
22 | public final class NoSuchUniqueName extends NoSuchElementException {
23 |
24 | /** The 'kind' of the table. */
25 | private final String kind;
26 | /** The name that couldn't be found. */
27 | private final String name;
28 |
29 | /**
30 | * Constructor.
31 | *
32 | * @param kind The kind of unique ID that triggered the exception.
33 | * @param name The name that couldn't be found.
34 | */
35 | public NoSuchUniqueName(final String kind, final String name) {
36 | super("No such name for '" + kind + "': '" + name + "'");
37 | this.kind = kind;
38 | this.name = name;
39 | }
40 |
41 | /** Returns the kind of unique ID that couldn't be found. */
42 | public String kind() {
43 | return kind;
44 | }
45 |
46 | /** Returns the name for which the unique ID couldn't be found. */
47 | public String name() {
48 | return name;
49 | }
50 |
51 | static final long serialVersionUID = 1266815261;
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/third_party/protobuf/include.mk:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2013 The Async HBase Authors. All rights reserved.
2 | # This file is part of Async HBase.
3 | #
4 | # Redistribution and use in source and binary forms, with or without
5 | # modification, are permitted provided that the following conditions are met:
6 | # - Redistributions of source code must retain the above copyright notice,
7 | # this list of conditions and the following disclaimer.
8 | # - Redistributions in binary form must reproduce the above copyright notice,
9 | # this list of conditions and the following disclaimer in the documentation
10 | # and/or other materials provided with the distribution.
11 | # - Neither the name of the StumbleUpon nor the names of its contributors
12 | # may be used to endorse or promote products derived from this software
13 | # without specific prior written permission.
14 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
18 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 | # POSSIBILITY OF SUCH DAMAGE.
25 |
26 | PROTOBUF_VERSION := 2.5.0
27 | PROTOBUF := third_party/protobuf/protobuf-java-$(PROTOBUF_VERSION).jar
28 | PROTOBUF_BASE_URL := http://search.maven.org/remotecontent?filepath=com/google/protobuf/protobuf-java/$(PROTOBUF_VERSION)
29 |
30 | $(PROTOBUF): $(PROTOBUF).md5
31 | set dummy "$(PROTOBUF_BASE_URL)" "$(PROTOBUF)"; shift; $(FETCH_DEPENDENCY)
32 |
33 | THIRD_PARTY += $(PROTOBUF)
34 |
--------------------------------------------------------------------------------
/src/core/Const.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2010-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.core;
14 |
15 | /** Constants used in various places. */
16 | public final class Const {
17 |
18 | /** Number of bytes on which a timestamp is encoded. */
19 | public static final short TIMESTAMP_BYTES = 4;
20 |
21 | /** Maximum number of tags allowed per data point. */
22 | public static final short MAX_NUM_TAGS = 8;
23 | // 8 is an aggressive limit on purpose. Can always be increased later.
24 |
25 | /** Number of LSBs in time_deltas reserved for flags. */
26 | static final short FLAG_BITS = 4;
27 |
28 | /**
29 | * When this bit is set, the value is a floating point value.
30 | * Otherwise it's an integer value.
31 | */
32 | static final short FLAG_FLOAT = 0x8;
33 |
34 | /** Mask to select the size of a value from the qualifier. */
35 | static final short LENGTH_MASK = 0x7;
36 |
37 | /** Mask to select all the FLAG_BITS. */
38 | static final short FLAGS_MASK = FLAG_FLOAT | LENGTH_MASK;
39 |
40 | /** Max time delta (in seconds) we can store in a column qualifier. */
41 | public static final short MAX_TIMESPAN = 3600;
42 |
43 | /**
44 | * Array containing the hexadecimal characters (0 to 9, A to F).
45 | * This array is read-only, changing its contents leads to an undefined
46 | * behavior.
47 | */
48 | public static final byte[] HEX = {
49 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
50 | 'A', 'B', 'C', 'D', 'E', 'F'
51 | };
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/src/core/DataPoint.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2010-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.core;
14 |
15 | /**
16 | * Represents a single data point.
17 | *
18 | * Implementations of this interface aren't expected to be synchronized.
19 | */
20 | public interface DataPoint {
21 |
22 | /**
23 | * Returns the timestamp (in seconds) associated with this data point.
24 | * @return A strictly positive, 32 bit integer.
25 | */
26 | long timestamp();
27 |
28 | /**
29 | * Tells whether or not the this data point is a value of integer type.
30 | * @return {@code true} if the {@code i}th value is of integer type,
31 | * {@code false} if it's of doubleing point type.
32 | */
33 | boolean isInteger();
34 |
35 | /**
36 | * Returns the value of the this data point as a {@code long}.
37 | * @throws ClassCastException if the {@code isInteger() == false}.
38 | */
39 | long longValue();
40 |
41 | /**
42 | * Returns the value of the this data point as a {@code double}.
43 | * @throws ClassCastException if the {@code isInteger() == true}.
44 | */
45 | double doubleValue();
46 |
47 | /**
48 | * Returns the value of the this data point as a {@code double}, even if
49 | * it's a {@code long}.
50 | * @return When {@code isInteger() == false}, this method returns the same
51 | * thing as {@link #doubleValue}. Otherwise, it returns the same thing as
52 | * {@link #longValue}'s return value casted to a {@code double}.
53 | */
54 | double toDouble();
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/src/tsd/client/ValidatedTextBox.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2010-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package tsd.client;
14 |
15 | import com.google.gwt.event.dom.client.BlurEvent;
16 | import com.google.gwt.event.dom.client.BlurHandler;
17 | import com.google.gwt.user.client.Command;
18 | import com.google.gwt.user.client.DeferredCommand;
19 | import com.google.gwt.user.client.ui.TextBox;
20 |
21 | class ValidatedTextBox extends TextBox implements BlurHandler {
22 |
23 | private String regexp;
24 |
25 | public ValidatedTextBox() {
26 | }
27 |
28 | public void setValidationRegexp(final String regexp) {
29 | if (this.regexp == null) { // First call to this method.
30 | super.addBlurHandler(this);
31 | }
32 | this.regexp = regexp;
33 | }
34 |
35 | public String getValidationRegexp() {
36 | return regexp;
37 | }
38 |
39 | public void onBlur(final BlurEvent event) {
40 | final String interval = getText();
41 | if (!interval.matches(regexp)) {
42 | // Steal the dateBoxFormatError :)
43 | addStyleName("dateBoxFormatError");
44 | event.stopPropagation();
45 | DeferredCommand.addCommand(new Command() {
46 | public void execute() {
47 | // TODO(tsuna): Understand why this doesn't work as expected, even
48 | // though we cancel the onBlur event and we put the focus afterwards
49 | // using a deferred command.
50 | //setFocus(true);
51 | selectAll();
52 | }
53 | });
54 | } else {
55 | removeStyleName("dateBoxFormatError");
56 | }
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/src/tsd/StaticFileRpc.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2010-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.tsd;
14 |
15 | import java.io.IOException;
16 |
17 | import net.opentsdb.core.TSDB;
18 |
19 | /** Implements the "/s" endpoint to serve static files. */
20 | final class StaticFileRpc implements HttpRpc {
21 |
22 | /**
23 | * The path to the directory where to find static files
24 | * (for the {@code /s} URLs).
25 | */
26 | private final String staticroot;
27 |
28 | /**
29 | * Constructor.
30 | */
31 | public StaticFileRpc() {
32 | staticroot = RpcHandler.getDirectoryFromSystemProp("tsd.http.staticroot");
33 | }
34 |
35 | public void execute(final TSDB tsdb, final HttpQuery query)
36 | throws IOException {
37 | final String uri = query.request().getUri();
38 | if ("/favicon.ico".equals(uri)) {
39 | query.sendFile(staticroot + "/favicon.ico", 31536000 /*=1yr*/);
40 | return;
41 | }
42 | if (uri.length() < 3) { // Must be at least 3 because of the "/s/".
43 | throw new BadRequestException("URI too short " + uri + "");
44 | }
45 | // Cheap security check to avoid directory traversal attacks.
46 | // TODO(tsuna): This is certainly not sufficient.
47 | if (uri.indexOf("..", 3) > 0) {
48 | throw new BadRequestException("Malformed URI " + uri + "");
49 | }
50 | final int questionmark = uri.indexOf('?', 3);
51 | final int pathend = questionmark > 0 ? questionmark : uri.length();
52 | query.sendFile(staticroot + uri.substring(3, pathend),
53 | uri.contains("nocache") ? 0 : 31536000 /*=1yr*/);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/opentsdb.spec.in:
--------------------------------------------------------------------------------
1 | # Put the RPM in the current directory.
2 | %define _rpmdir .
3 | # Find the tarball produced by `make dist' in the current directory.
4 | %define _sourcedir %(echo $PWD)
5 |
6 | Name: @PACKAGE@
7 | Version: @VERSION@
8 | Release: 1
9 | Summary: A scalable, distributed Time Series Database
10 | Packager: @PACKAGE_BUGREPORT@
11 | BuildArch: noarch
12 | Group: Service/Monitoring
13 | License: LGPLv2.1+
14 | URL: http://opentsdb.net
15 | Source: @PACKAGE@-@VERSION@.tar.gz
16 |
17 | Requires: gnuplot
18 |
19 | # Disable the stupid stuff rpm distros include in the build process by
20 | # default:
21 | # Disable any prep shell actions. replace them with simply 'true'
22 | %define __spec_prep_pre true
23 | %define __spec_prep_post true
24 | # Disable any build shell actions. replace them with simply 'true'
25 | %define __spec_build_pre cd %{_builddir}
26 | %define __spec_build_post true
27 | # Disable any install shell actions. replace them with simply 'true'
28 | %define __spec_install_pre cd %{_builddir}
29 | %define __spec_install_post true
30 | # Disable any clean shell actions. replace them with simply 'true'
31 | %define __spec_clean_pre cd %{_builddir}
32 | %define __spec_clean_post true
33 |
34 |
35 | %description
36 | OpenTSDB is a distributed, scalable Time Series Database (TSDB) written on top
37 | of HBase. OpenTSDB was written to address a common need: store, index and
38 | serve metrics collected from computer systems (network gear, operating
39 | systems, applications) at a large scale, and make this data easily accessible
40 | and graphable.
41 |
42 | Thanks to HBase's scalability, OpenTSDB allows you to collect many thousands
43 | of metrics from thousands of hosts and applications, at a high rate (every few
44 | seconds). OpenTSDB will never delete or downsample data and can easily store
45 | billions of data points.
46 |
47 | %prep
48 | %setup -q
49 |
50 |
51 | %build
52 | %configure
53 | make
54 |
55 | %install
56 | rm -rf %{buildroot}
57 | make install DESTDIR=%{buildroot}
58 | mkdir -p %{buildroot}/var/cache/opentsdb
59 |
60 |
61 | %clean
62 | rm -rf %{buildroot}
63 |
64 |
65 | %files
66 | %defattr(644,root,root,755)
67 | %attr(0755,root,root) %{_bindir}/*
68 | %attr(0755,root,root) %{_datarootdir}/opentsdb/*.sh
69 | %doc
70 | %{_datarootdir}/opentsdb
71 | %{_bindir}/tsdb
72 | %dir %{_localstatedir}/cache/opentsdb
73 |
74 |
75 | %changelog
76 |
77 |
--------------------------------------------------------------------------------
/NEWS:
--------------------------------------------------------------------------------
1 | OpenTSDB - User visible changes.
2 |
3 | * Version 1.1.1 (2013-??-??) [???????]
4 |
5 | Noteworthy changes:
6 | - UIDs are now assigned in a lock-less fashion.
7 |
8 |
9 | * Version 1.1.0 (2013-03-08) [12879d7]
10 |
11 | Noteworthy changes:
12 | - Licensing adjustment: allow LGPLv2.1+ in addition to LGPLv3+.
13 | - Various fixes used when customizing size of UniqueId. The default size
14 | is 3 bytes and is a compile-time constant rarely changed in practice.
15 | - New a new standard deviation aggregator, `dev'.
16 | - New `fgcolor', `bgcolor' and `smooth' query parameters to /q.
17 | - New `tz' query string parameter to allow specifying a custom time zone.
18 | - Stop accepting connections when shutting down.
19 | - A new `dropcaches' administrative command allows discarding in-memory
20 | caches. Right now these are UID mappings.
21 | - Browser history support in the web UI.
22 | - Allow "1d-ago" style input in date boxes.
23 | - Fix the 30d integer overflow in the web UI.
24 | - Add the ability to use mouse for drag-to-zoom on graphs.
25 | - Integration with Maven.
26 | - Work around a Netty performance bug, increasing write throughput by 10x.
27 | - Properly parse floating point values in scientific notations.
28 | - Allow tuning the number of worker threads or using OIO.
29 | - Fix auto-completion bug causing partial results to show in the web UI.
30 | - Various internal bug fixes.
31 | - Update all dependencies.
32 | - TSDB data compaction is now enabled by default.
33 |
34 |
35 | * Version 1.0.0 (2011-12-23) [66a6b42]
36 |
37 | Initial release:
38 | - Upload data points through a telnet-style protocol.
39 | - HTTP interface to query data in ASCII and PNG.
40 | - Efficient, fully asynchronous write path.
41 | - Synchronous / blocking read path (to be rewritten).
42 | - TSDB data compaction (disabled by default).
43 |
44 | -----
45 |
46 | Copyright (C) 2011-2012 The OpenTSDB Authors.
47 |
48 | This library is free software: you can redistribute it and/or modify it
49 | under the terms of the GNU Lesser General Public License as published
50 | by the Free Software Foundation, either version 2.1 of the License, or
51 | (at your option) any later version.
52 |
53 | This library is distributed in the hope that it will be useful,
54 | but WITHOUT ANY WARRANTY; without even the implied warranty of
55 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
56 | GNU Lesser General Public License for more details.
57 |
58 | You should have received a copy of the GNU Lesser General Public License
59 | along with this library. If not, see .
60 |
61 | Local Variables:
62 | mode: outline
63 | End:
64 |
--------------------------------------------------------------------------------
/tools/tsddrain.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | #
3 | # This little script can be used to replace TSDs while performing prolonged
4 | # HBase or HDFS maintenances. It runs a simple, low-end TCP server to accept
5 | # all the data points from tcollectors and dump them to a bunch of files, one
6 | # per client IP address. These files can then later be batch-imported, once
7 | # HBase is back up.
8 | #
9 | # This file is part of OpenTSDB.
10 | # Copyright (C) 2013 The OpenTSDB Authors.
11 | #
12 | # This library is free software: you can redistribute it and/or modify it
13 | # under the terms of the GNU Lesser General Public License as published
14 | # by the Free Software Foundation; either version 2.1 of the License, or
15 | # (at your option) any later version.
16 | #
17 | # This library is distributed in the hope that it will be useful,
18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 | # GNU Lesser General Public License for more details.
21 | #
22 | # You should have received a copy of the GNU Lesser General Public License
23 | # along with this library. If not, see .
24 |
25 | import os
26 | import socket
27 | import sys
28 | import SocketServer
29 |
30 | class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
31 | allow_reuse_address = True
32 |
33 |
34 | DRAINDIR = None
35 |
36 | class Handler(SocketServer.StreamRequestHandler):
37 | def handle(self):
38 | sys.stdout.write("O")
39 | sys.stdout.flush()
40 | out = open(os.path.join(DRAINDIR, self.client_address[0]), "a")
41 | n = 0
42 | while True:
43 | req = self.rfile.readline()
44 | if not req:
45 | break
46 | if req == "version\n":
47 | self.wfile.write("drain.py\n")
48 | sys.stdout.write("V")
49 | sys.stdout.flush()
50 | continue
51 | if not req.startswith("put "):
52 | sys.stdout.write("!")
53 | sys.stdout.flush()
54 | continue
55 | out.write(req[4:])
56 | out.flush()
57 | if n % 100 == 0:
58 | sys.stdout.write(".")
59 | sys.stdout.flush()
60 | n += 1
61 | out.close()
62 | sys.stdout.write("X")
63 | sys.stdout.flush()
64 |
65 |
66 | def main(args):
67 | if len(args) != 3:
68 | print >> sys.stderr, "Usage: %s " % args[0]
69 | return 1
70 | global DRAINDIR
71 | port = int(args[1])
72 | DRAINDIR = args[2]
73 | if not os.path.isdir(DRAINDIR):
74 | os.makedirs(DRAINDIR)
75 | server = ThreadedTCPServer(("0.0.0.0", port), Handler)
76 | try:
77 | print "Use Ctrl-C to stop me."
78 | server.serve_forever()
79 | except KeyboardInterrupt:
80 | pass
81 |
82 |
83 | if __name__ == "__main__":
84 | sys.exit(main(sys.argv))
85 |
--------------------------------------------------------------------------------
/src/core/Aggregator.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2010-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.core;
14 |
15 | import java.util.NoSuchElementException;
16 |
17 | /**
18 | * A function capable of aggregating multiple {@link DataPoints} together.
19 | *
20 | * All aggregators must be stateless. All they can do is run through a
21 | * sequence of {@link Longs Longs} or {@link Doubles Doubles} and return an
22 | * aggregated value.
23 | */
24 | public interface Aggregator {
25 |
26 | /**
27 | * A sequence of {@code long}s.
28 | *
29 | * This interface is semantically equivalent to
30 | * {@code Iterator}.
31 | */
32 | public interface Longs {
33 |
34 | /**
35 | * Returns {@code true} if this sequence has more values.
36 | * {@code false} otherwise.
37 | */
38 | boolean hasNextValue();
39 |
40 | /**
41 | * Returns the next {@code long} value in this sequence.
42 | * @throws NoSuchElementException if calling {@link #hasNextValue} returns
43 | * {@code false}.
44 | */
45 | long nextLongValue();
46 |
47 | }
48 |
49 | /**
50 | * A sequence of {@code double}s.
51 | *
52 | * This interface is semantically equivalent to
53 | * {@code Iterator}.
54 | */
55 | public interface Doubles {
56 |
57 | /**
58 | * Returns {@code true} if this sequence has more values.
59 | * {@code false} otherwise.
60 | */
61 | boolean hasNextValue();
62 |
63 | /**
64 | * Returns the next {@code double} value in this sequence.
65 | * @throws NoSuchElementException if calling {@link #hasNextValue} returns
66 | * {@code false}.
67 | */
68 | double nextDoubleValue();
69 |
70 | }
71 |
72 | /**
73 | * Aggregates a sequence of {@code long}s.
74 | * @param values The sequence to aggregate.
75 | * @return The aggregated value.
76 | */
77 | long runLong(Longs values);
78 |
79 | /**
80 | * Aggregates a sequence of {@code double}s.
81 | * @param values The sequence to aggregate.
82 | * @return The aggregated value.
83 | */
84 | double runDouble(Doubles values);
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/tsdb.in:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 | me=`basename "$0"`
5 | mydir=`dirname "$0"`
6 | # Either:
7 | # abs_srcdir and abs_builddir are set: we're running in a dev tree
8 | # or pkgdatadir is set: we've been installed, we respect that.
9 | abs_srcdir='@abs_srcdir@'
10 | abs_builddir='@abs_builddir@'
11 | pkgdatadir='@pkgdatadir@'
12 | # Either we've been installed and pkgdatadir exists, or we haven't been
13 | # installed and abs_srcdir / abs_builddir aren't empty.
14 | test -d "$pkgdatadir" || test -n "$abs_srcdir$abs_builddir" || {
15 | echo >&2 "$me: Uh-oh, \`$pkgdatadir' doesn't exist, is OpenTSDB properly installed?"
16 | exit 1
17 | }
18 |
19 | if test -n "$pkgdatadir"; then
20 | localdir="$pkgdatadir"
21 | for jar in "$pkgdatadir"/*.jar; do
22 | CLASSPATH="$CLASSPATH:$jar"
23 | done
24 | # Add pkgdatadir itself so we can find logback.xml
25 | CLASSPATH="$CLASSPATH:$pkgdatadir"
26 | else
27 | localdir="$abs_builddir"
28 | # If we're running out of the build tree, it's especially important that we
29 | # know exactly what jars we need to build the CLASSPATH. Otherwise people
30 | # cannot easily pick up new dependencies as we might mix multiple versions
31 | # of the same dependencies on the CLASSPATH, which is bad. Looking for a
32 | # specific version of each jar prevents this problem.
33 | # TODO(tsuna): Once we jarjar all the dependencies together, this will no
34 | # longer be an issue. See issue #23.
35 | for jar in `make -C "$abs_builddir" printdeps | sed '/third_party.*jar/!d'`; do
36 | for dir in "$abs_builddir" "$abs_srcdir"; do
37 | test -f "$dir/$jar" && CLASSPATH="$CLASSPATH:$dir/$jar" && continue 2
38 | done
39 | echo >&2 "$me: error: Couldn't find \`$jar' either under \`$abs_builddir' or \`$abs_srcdir'."
40 | exit 2
41 | done
42 | # Add the src dir so we can find logback.xml
43 | CLASSPATH="$CLASSPATH:$abs_srcdir/src"
44 | fi
45 | # Remove any leading colon.
46 | CLASSPATH="${CLASSPATH#:}"
47 |
48 | usage() {
49 | echo >&2 "usage: $me [args]"
50 | echo 'Valid commands: fsck, import, mkmetric, query, tsd, scan, uid'
51 | exit 1
52 | }
53 |
54 | case $1 in
55 | (fsck)
56 | MAINCLASS=Fsck
57 | ;;
58 | (import)
59 | MAINCLASS=TextImporter
60 | ;;
61 | (mkmetric)
62 | shift
63 | set uid assign metrics "$@"
64 | MAINCLASS=UidManager
65 | ;;
66 | (query)
67 | MAINCLASS=CliQuery
68 | ;;
69 | (tsd)
70 | MAINCLASS=TSDMain
71 | ;;
72 | (scan)
73 | MAINCLASS=DumpSeries
74 | ;;
75 | (uid)
76 | MAINCLASS=UidManager
77 | ;;
78 | (*)
79 | echo >&2 "$me: error: unknown command '$1'"
80 | usage
81 | ;;
82 | esac
83 | shift
84 |
85 | JAVA=${JAVA-'java'}
86 | JVMARGS=${JVMARGS-'-enableassertions -enablesystemassertions'}
87 | test -r "$localdir/tsdb.local" && . "$localdir/tsdb.local"
88 | exec $JAVA $JVMARGS -classpath "$CLASSPATH" net.opentsdb.tools.$MAINCLASS "$@"
89 |
--------------------------------------------------------------------------------
/src/core/SeekableView.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2010-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.core;
14 |
15 | import java.util.Iterator;
16 | import java.util.NoSuchElementException;
17 |
18 | /**
19 | * Provides a zero-copy view to iterate through data points.
20 | *
21 | * The iterator returned by classes that implement this interface must return
22 | * each {@link DataPoint} in {@code O(1)} and does not support {@link #remove}.
23 | *
24 | * Because no data is copied during iteration and no new object gets created,
25 | * the {@link DataPoint} returned must not be stored and gets
26 | * invalidated as soon as {@link #next} is called on the iterator (actually it
27 | * doesn't get invalidated but rather its contents changes). If you want to
28 | * store individual data points, you need to copy the timestamp and value out
29 | * of each {@link DataPoint} into your own data structures.
30 | *
31 | * In the vast majority of cases, the iterator will be used to go once through
32 | * all the data points, which is why it's not a problem if the iterator acts
33 | * just as a transient "view". Iterating will be very cheap since no memory
34 | * allocation is required (except to instantiate the actual iterator at the
35 | * beginning).
36 | */
37 | public interface SeekableView extends Iterator {
38 |
39 | /**
40 | * Returns {@code true} if this view has more elements.
41 | */
42 | boolean hasNext();
43 |
44 | /**
45 | * Returns a view on the next data point.
46 | * No new object gets created, the referenced returned is always the same
47 | * and must not be stored since its internal data structure will change the
48 | * next time {@code next()} is called.
49 | * @throws NoSuchElementException if there were no more elements to iterate
50 | * on (in which case {@link #hasNext} would have returned {@code false}.
51 | */
52 | DataPoint next();
53 |
54 | /**
55 | * Unsupported operation.
56 | * @throws UnsupportedOperationException always.
57 | */
58 | void remove();
59 |
60 | /**
61 | * Advances the iterator to the given point in time.
62 | *
63 | * This allows the iterator to skip all the data points that are strictly
64 | * before the given timestamp.
65 | * @param timestamp A strictly positive 32 bit UNIX timestamp (in seconds).
66 | * @throws IllegalArgumentException if the timestamp is zero, or negative,
67 | * or doesn't fit on 32 bits (think "unsigned int" -- yay Java!).
68 | */
69 | void seek(long timestamp);
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/src/tsd/client/QueryString.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package tsd.client;
14 |
15 | // I (tsuna) originally wrote this code for Netty. Surprisingly, GWT has
16 | // nothing to manually parse query string parameters...
17 |
18 | import java.util.ArrayList;
19 | import java.util.HashMap;
20 |
21 | /**
22 | * Splits an HTTP query string into a path string and key-value parameter pairs.
23 | */
24 | public final class QueryString extends HashMap> {
25 |
26 | /**
27 | * Returns the decoded key-value parameter pairs of the URI.
28 | */
29 | public static QueryString decode(final String s) {
30 | final QueryString params = new QueryString();
31 | String name = null;
32 | int pos = 0; // Beginning of the unprocessed region
33 | int i; // End of the unprocessed region
34 | for (i = 0; i < s.length(); i++) {
35 | final char c = s.charAt(i);
36 | if (c == '=' && name == null) {
37 | if (pos != i) {
38 | name = s.substring(pos, i);
39 | }
40 | pos = i + 1;
41 | } else if (c == '&') {
42 | if (name == null && pos != i) {
43 | // We haven't seen an `=' so far but moved forward.
44 | // Must be a param of the form '&a&' so add it with
45 | // an empty value.
46 | params.add(s.substring(pos, i), "");
47 | } else if (name != null) {
48 | params.add(name, s.substring(pos, i));
49 | name = null;
50 | }
51 | pos = i + 1;
52 | }
53 | }
54 |
55 | if (pos != i) { // Are there characters we haven't dealt with?
56 | if (name == null) { // Yes and we haven't seen any `='.
57 | params.add(s.substring(pos, i), "");
58 | } else { // Yes and this must be the last value.
59 | params.add(name, s.substring(pos, i));
60 | }
61 | } else if (name != null) { // Have we seen a name without value?
62 | params.add(name, "");
63 | }
64 |
65 | return params;
66 | }
67 |
68 | /**
69 | * Adds a query string element.
70 | * @param name The name of the element.
71 | * @param value The value of the element.
72 | */
73 | public void add(final String name, final String value) {
74 | ArrayList values = super.get(name);
75 | if (values == null) {
76 | values = new ArrayList(1); // Often there's only 1 value.
77 | super.put(name, values);
78 | }
79 | values.add(value);
80 | }
81 |
82 | /**
83 | * Returns the first value for the given key, or {@code null}.
84 | */
85 | public String getFirst(final String key) {
86 | final ArrayList values = super.get(key);
87 | return values == null ? null : values.get(0);
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/src/tsd/client/EventsHandler.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2010-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package tsd.client;
14 |
15 | import com.google.gwt.event.dom.client.BlurEvent;
16 | import com.google.gwt.event.dom.client.BlurHandler;
17 | import com.google.gwt.event.dom.client.ChangeEvent;
18 | import com.google.gwt.event.dom.client.ChangeHandler;
19 | import com.google.gwt.event.dom.client.ClickEvent;
20 | import com.google.gwt.event.dom.client.ClickHandler;
21 | import com.google.gwt.event.dom.client.DomEvent;
22 | import com.google.gwt.event.dom.client.KeyCodes;
23 | import com.google.gwt.event.dom.client.KeyPressEvent;
24 | import com.google.gwt.event.dom.client.KeyPressHandler;
25 | import com.google.gwt.event.shared.EventHandler;
26 | import com.google.gwt.user.client.Command;
27 | import com.google.gwt.user.client.DeferredCommand;
28 |
29 | /**
30 | * Handler for multiple events that indicate that the widget may have changed.
31 | *
32 | * This handler is just a convenient 4-in-1 handler that can be re-used on a
33 | * wide range of widgets such as {@code TextBox} and its derivative,
34 | * {@code CheckBox}, {@code ListBox} etc.
35 | */
36 | abstract class EventsHandler implements BlurHandler, ChangeHandler,
37 | ClickHandler, KeyPressHandler {
38 |
39 | /**
40 | * Called after one of the events (click, blur, change or "enter"
41 | * is pressed) occurs.
42 | *
43 | * This method is NOT called while the event is happening. It's invoked via
44 | * a {@link DeferredCommand deferred command}. This entails that the event
45 | * can't be cancelled as it has already executed. The reason the call is
46 | * deferred is that this way, things like {@code SuggestBox}s will have
47 | * already done their auto-completion by the time this method is called, and
48 | * thus the handler will see the suggested text instead of the partial input
49 | * being typed by the user.
50 | * @param event The event that occurred.
51 | */
52 | protected abstract void onEvent(DomEvent event);
53 |
54 | public final void onClick(final ClickEvent event) {
55 | scheduleEvent(event);
56 | }
57 |
58 | public final void onBlur(final BlurEvent event) {
59 | scheduleEvent(event);
60 | }
61 |
62 | public final void onChange(final ChangeEvent event) {
63 | scheduleEvent(event);
64 | }
65 |
66 | public final void onKeyPress(final KeyPressEvent event) {
67 | if (event.getCharCode() == KeyCodes.KEY_ENTER) {
68 | scheduleEvent(event);
69 | }
70 | }
71 |
72 | /** Executes the event using a deferred command. */
73 | private void scheduleEvent(final DomEvent event) {
74 | DeferredCommand.addCommand(new Command() {
75 | public void execute() {
76 | onEvent(event);
77 | }
78 | });
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/src/uid/UniqueIdInterface.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2010-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.uid;
14 |
15 | import org.hbase.async.HBaseException;
16 |
17 | /**
18 | * Represents a table of Unique IDs, manages the lookup and creation of IDs.
19 | *
20 | * For efficiency, various kinds of "names" need to be mapped to small, unique
21 | * IDs. For instance, we give a unique ID to each metric name, to each tag
22 | * name, to each tag value.
23 | *
24 | * An instance of this class handles the unique IDs for one kind of ID. For
25 | * example:
26 | *
27 | * UniqueId metric_names = ...;
28 | * byte[] id = metric_names.get("sys.net.rx_bytes");
29 | *
30 | *
31 | * IDs are looked up in HBase and cached forever in memory (since they're
32 | * immutable). IDs are encoded on a fixed number of bytes, which is
33 | * implementation dependent.
34 | */
35 | public interface UniqueIdInterface {
36 |
37 | /**
38 | * Returns what kind of Unique ID is served by this instance.
39 | */
40 | String kind();
41 |
42 | /**
43 | * Returns the number of bytes on which each Unique ID is encoded.
44 | */
45 | short width();
46 |
47 | /**
48 | * Finds the name associated with a given ID.
49 | *
50 | * @param id The ID associated with that name.
51 | * @see #getId(String)
52 | * @see #getOrCreateId(String)
53 | * @throws NoSuchUniqueId if the given ID is not assigned.
54 | * @throws HBaseException if there is a problem communicating with HBase.
55 | * @throws IllegalArgumentException if the ID given in argument is encoded
56 | * on the wrong number of bytes.
57 | */
58 | String getName(byte[] id) throws NoSuchUniqueId, HBaseException;
59 |
60 | /**
61 | * Finds the ID associated with a given name.
62 | *
63 | * The length of the byte array is fixed in advance by the implementation.
64 | *
65 | * @param name The name to lookup in the table.
66 | * @see #getName(byte[])
67 | * @return A non-null, non-empty {@code byte[]} array.
68 | * @throws NoSuchUniqueName if the name requested doesn't have an ID assigned.
69 | * @throws HBaseException if there is a problem communicating with HBase.
70 | * @throws IllegalStateException if the ID found in HBase is encoded on the
71 | * wrong number of bytes.
72 | */
73 | byte[] getId(String name) throws NoSuchUniqueName, HBaseException;
74 |
75 | /**
76 | * Finds the ID associated with a given name or creates it.
77 | *
78 | * The length of the byte array is fixed in advance by the implementation.
79 | *
80 | * @param name The name to lookup in the table or to assign an ID to.
81 | * @throws HBaseException if there is a problem communicating with HBase.
82 | * @throws IllegalStateException if all possible IDs are already assigned.
83 | * @throws IllegalStateException if the ID found in HBase is encoded on the
84 | * wrong number of bytes.
85 | */
86 | byte[] getOrCreateId(String name) throws HBaseException, IllegalStateException;
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/src/tsd/LineBasedFrameDecoder.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.tsd;
14 |
15 | import org.jboss.netty.buffer.ChannelBuffer;
16 | import org.jboss.netty.channel.Channel;
17 | import org.jboss.netty.channel.Channels;
18 | import org.jboss.netty.channel.ChannelHandlerContext;
19 | import org.jboss.netty.handler.codec.frame.FrameDecoder;
20 | import org.jboss.netty.handler.codec.frame.TooLongFrameException;
21 |
22 | /**
23 | * Decodes telnet-style frames delimited by new-lines.
24 | *
25 | * Both "\n" and "\r\n" are handled.
26 | *
27 | * This decoder is stateful and is thus NOT shareable.
28 | */
29 | final class LineBasedFrameDecoder extends FrameDecoder {
30 |
31 | /** Maximum length of a frame we're willing to decode. */
32 | private final int max_length;
33 | /** True if we're discarding input because we're already over max_length. */
34 | private boolean discarding;
35 |
36 | /**
37 | * Creates a new decoder.
38 | * @param max_length Maximum length of a frame we're willing to decode.
39 | * If a frame is longer than that, a {@link TooLongFrameException} will
40 | * be fired on the channel causing it.
41 | */
42 | public LineBasedFrameDecoder(final int max_length) {
43 | this.max_length = max_length;
44 | }
45 |
46 | @Override
47 | protected Object decode(final ChannelHandlerContext ctx,
48 | final Channel channel,
49 | final ChannelBuffer buffer) throws Exception {
50 | final int eol = findEndOfLine(buffer);
51 | if (eol != -1) {
52 | final ChannelBuffer frame;
53 | final int length = eol - buffer.readerIndex();
54 | assert length >= 0: "WTF? length=" + length;
55 | if (discarding) {
56 | frame = null;
57 | buffer.skipBytes(length);
58 | } else {
59 | frame = buffer.readBytes(length);
60 | }
61 | final byte delim = buffer.readByte();
62 | if (delim == '\r') {
63 | buffer.skipBytes(1); // Skip the \n.
64 | }
65 | return frame;
66 | }
67 |
68 | final int buffered = buffer.readableBytes();
69 | if (!discarding && buffered > max_length) {
70 | discarding = true;
71 | Channels.fireExceptionCaught(ctx.getChannel(),
72 | new TooLongFrameException("Frame length exceeds " + max_length + " ("
73 | + buffered + " bytes buffered already)"));
74 | }
75 | if (discarding) {
76 | buffer.skipBytes(buffer.readableBytes());
77 | }
78 | return null;
79 | }
80 |
81 | /**
82 | * Returns the index in the buffer of the end of line found.
83 | * Returns -1 if no end of line was found in the buffer.
84 | */
85 | private static int findEndOfLine(final ChannelBuffer buffer) {
86 | final int n = buffer.writerIndex();
87 | for (int i = buffer.readerIndex(); i < n; i ++) {
88 | final byte b = buffer.getByte(i);
89 | if (b == '\n') {
90 | return i;
91 | } else if (b == '\r' && i < n - 1 && buffer.getByte(i + 1) == '\n') {
92 | return i; // \r\n
93 | }
94 | }
95 | return -1; // Not found.
96 | }
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/src/tsd/ConnectionManager.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2010-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.tsd;
14 |
15 | import java.io.IOException;
16 | import java.nio.channels.ClosedChannelException;
17 | import java.util.concurrent.atomic.AtomicLong;
18 |
19 | import org.slf4j.Logger;
20 | import org.slf4j.LoggerFactory;
21 |
22 | import org.jboss.netty.channel.Channel;
23 | import org.jboss.netty.channel.ChannelEvent;
24 | import org.jboss.netty.channel.ChannelHandlerContext;
25 | import org.jboss.netty.channel.ChannelStateEvent;
26 | import org.jboss.netty.channel.ExceptionEvent;
27 | import org.jboss.netty.channel.SimpleChannelHandler;
28 | import org.jboss.netty.channel.group.DefaultChannelGroup;
29 |
30 | import net.opentsdb.stats.StatsCollector;
31 |
32 | /**
33 | * Keeps track of all existing connections.
34 | */
35 | final class ConnectionManager extends SimpleChannelHandler {
36 |
37 | private static final Logger LOG = LoggerFactory.getLogger(ConnectionManager.class);
38 |
39 | private static final AtomicLong connections_established = new AtomicLong();
40 | private static final AtomicLong exceptions_caught = new AtomicLong();
41 |
42 | private static final DefaultChannelGroup channels =
43 | new DefaultChannelGroup("all-channels");
44 |
45 | static void closeAllConnections() {
46 | channels.close().awaitUninterruptibly();
47 | }
48 |
49 | /** Constructor. */
50 | public ConnectionManager() {
51 | }
52 |
53 | /**
54 | * Collects the stats and metrics tracked by this instance.
55 | * @param collector The collector to use.
56 | */
57 | public static void collectStats(final StatsCollector collector) {
58 | collector.record("connectionmgr.connections", connections_established);
59 | collector.record("connectionmgr.exceptions", exceptions_caught);
60 | }
61 |
62 | @Override
63 | public void channelOpen(final ChannelHandlerContext ctx,
64 | final ChannelStateEvent e) {
65 | channels.add(e.getChannel());
66 | connections_established.incrementAndGet();
67 | }
68 |
69 | @Override
70 | public void handleUpstream(final ChannelHandlerContext ctx,
71 | final ChannelEvent e) throws Exception {
72 | if (e instanceof ChannelStateEvent) {
73 | LOG.info(e.toString());
74 | }
75 | super.handleUpstream(ctx, e);
76 | }
77 |
78 | @Override
79 | public void exceptionCaught(final ChannelHandlerContext ctx,
80 | final ExceptionEvent e) {
81 | final Throwable cause = e.getCause();
82 | final Channel chan = ctx.getChannel();
83 | exceptions_caught.incrementAndGet();
84 | if (cause instanceof ClosedChannelException) {
85 | LOG.warn("Attempt to write to closed channel " + chan);
86 | return;
87 | }
88 | if (cause instanceof IOException) {
89 | final String message = cause.getMessage();
90 | if ("Connection reset by peer".equals(message)
91 | || "Connection timed out".equals(message)) {
92 | // Do nothing. A client disconnecting isn't really our problem. Oh,
93 | // and I'm not kidding you, there's no better way to detect ECONNRESET
94 | // in Java. Like, people have been bitching about errno for years,
95 | // and Java managed to do something *far* worse. That's quite a feat.
96 | return;
97 | }
98 | }
99 | LOG.error("Unexpected exception from downstream for " + chan, cause);
100 | e.getChannel().close();
101 | }
102 |
103 | }
104 |
--------------------------------------------------------------------------------
/src/tsd/PipelineFactory.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2010-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.tsd;
14 |
15 | import static org.jboss.netty.channel.Channels.pipeline;
16 | import org.jboss.netty.buffer.ChannelBuffer;
17 | import org.jboss.netty.channel.Channel;
18 | import org.jboss.netty.channel.ChannelHandlerContext;
19 | import org.jboss.netty.channel.ChannelPipeline;
20 | import org.jboss.netty.channel.ChannelPipelineFactory;
21 | import org.jboss.netty.handler.codec.frame.FrameDecoder;
22 | import org.jboss.netty.handler.codec.string.StringEncoder;
23 | import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
24 | import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
25 |
26 | import net.opentsdb.core.TSDB;
27 |
28 | /**
29 | * Creates a newly configured {@link ChannelPipeline} for a new channel.
30 | * This class is supposed to be a singleton.
31 | */
32 | public final class PipelineFactory implements ChannelPipelineFactory {
33 |
34 | // Those are entirely stateless and thus a single instance is needed.
35 | private static final StringEncoder ENCODER = new StringEncoder();
36 | private static final WordSplitter DECODER = new WordSplitter();
37 |
38 | // Those are sharable but maintain some state, so a single instance per
39 | // PipelineFactory is needed.
40 | private final ConnectionManager connmgr = new ConnectionManager();
41 | private final DetectHttpOrRpc HTTP_OR_RPC = new DetectHttpOrRpc();
42 |
43 | /** Stateless handler for RPCs. */
44 | private final RpcHandler rpchandler;
45 |
46 | /**
47 | * Constructor.
48 | * @param tsdb The TSDB to use.
49 | */
50 | public PipelineFactory(final TSDB tsdb) {
51 | this.rpchandler = new RpcHandler(tsdb);
52 | }
53 |
54 | @Override
55 | public ChannelPipeline getPipeline() throws Exception {
56 | final ChannelPipeline pipeline = pipeline();
57 |
58 | pipeline.addLast("connmgr", connmgr);
59 | pipeline.addLast("detect", HTTP_OR_RPC);
60 | return pipeline;
61 | }
62 |
63 | /**
64 | * Dynamically changes the {@link ChannelPipeline} based on the request.
65 | * If a request uses HTTP, then this changes the pipeline to process HTTP.
66 | * Otherwise, the pipeline is changed to processes an RPC.
67 | */
68 | final class DetectHttpOrRpc extends FrameDecoder {
69 |
70 | @Override
71 | protected Object decode(final ChannelHandlerContext ctx,
72 | final Channel chan,
73 | final ChannelBuffer buffer) throws Exception {
74 | if (buffer.readableBytes() < 1) { // Yes sometimes we can be called
75 | return null; // with an empty buffer...
76 | }
77 |
78 | final int firstbyte = buffer.getUnsignedByte(buffer.readerIndex());
79 | final ChannelPipeline pipeline = ctx.getPipeline();
80 | // None of the commands in the RPC protocol start with a capital ASCII
81 | // letter for the time being, and all HTTP commands do (GET, POST, etc.)
82 | // so use this as a cheap way to differentiate the two.
83 | if ('A' <= firstbyte && firstbyte <= 'Z') {
84 | pipeline.addLast("decoder", new HttpRequestDecoder());
85 | pipeline.addLast("encoder", new HttpResponseEncoder());
86 | } else {
87 | pipeline.addLast("framer", new LineBasedFrameDecoder(1024));
88 | pipeline.addLast("encoder", ENCODER);
89 | pipeline.addLast("decoder", DECODER);
90 | }
91 | pipeline.remove(this);
92 | pipeline.addLast("handler", rpchandler);
93 |
94 | // Forward the buffer to the next handler.
95 | return buffer.readBytes(buffer.readableBytes());
96 | }
97 |
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/src/tools/CliOptions.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2010-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.tools;
14 |
15 | import ch.qos.logback.classic.Logger;
16 | import ch.qos.logback.classic.Level;
17 |
18 | import org.slf4j.LoggerFactory;
19 |
20 | import org.jboss.netty.logging.InternalLoggerFactory;
21 | import org.jboss.netty.logging.Slf4JLoggerFactory;
22 |
23 | import org.hbase.async.HBaseClient;
24 |
25 | /** Helper functions to parse arguments passed to {@code main}. */
26 | final class CliOptions {
27 |
28 | static {
29 | InternalLoggerFactory.setDefaultFactory(new Slf4JLoggerFactory());
30 | }
31 |
32 | /** Adds common TSDB options to the given {@code argp}. */
33 | static void addCommon(final ArgP argp) {
34 | argp.addOption("--table", "TABLE",
35 | "Name of the HBase table where to store the time series"
36 | + " (default: tsdb).");
37 | argp.addOption("--uidtable", "TABLE",
38 | "Name of the HBase table to use for Unique IDs"
39 | + " (default: tsdb-uid).");
40 | argp.addOption("--zkquorum", "SPEC",
41 | "Specification of the ZooKeeper quorum to use"
42 | + " (default: localhost).");
43 | argp.addOption("--zkbasedir", "PATH",
44 | "Path under which is the znode for the -ROOT- region"
45 | + " (default: /hbase).");
46 | }
47 |
48 | /** Adds a --verbose flag. */
49 | static void addVerbose(final ArgP argp) {
50 | argp.addOption("--verbose",
51 | "Print more logging messages and not just errors.");
52 | argp.addOption("-v", "Short for --verbose.");
53 | }
54 |
55 | /** Adds the --auto-metric flag. */
56 | static void addAutoMetricFlag(final ArgP argp) {
57 | argp.addOption("--auto-metric", "Automatically add metrics to tsdb as they"
58 | + " are inserted. Warning: this may cause unexpected"
59 | + " metrics to be tracked");
60 | }
61 |
62 | /**
63 | * Parse the command line arguments with the given options.
64 | * @param options Options to parse in the given args.
65 | * @param args Command line arguments to parse.
66 | * @return The remainder of the command line or
67 | * {@code null} if {@code args} were invalid and couldn't be parsed.
68 | */
69 | static String[] parse(final ArgP argp, String[] args) {
70 | try {
71 | args = argp.parse(args);
72 | } catch (IllegalArgumentException e) {
73 | System.err.println("Invalid usage. " + e.getMessage());
74 | return null;
75 | }
76 | honorVerboseFlag(argp);
77 | return args;
78 | }
79 |
80 | /** Changes the log level to 'WARN' unless --verbose is passed. */
81 | private static void honorVerboseFlag(final ArgP argp) {
82 | if (argp.optionExists("--verbose") && !argp.has("--verbose")
83 | && !argp.has("-v")) {
84 | // SLF4J doesn't provide any API to programmatically set the logging
85 | // level of the underlying logging library. So we have to violate the
86 | // encapsulation provided by SLF4J.
87 | for (final Logger logger :
88 | ((Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME))
89 | .getLoggerContext().getLoggerList()) {
90 | logger.setLevel(Level.WARN);
91 | }
92 | }
93 | }
94 |
95 | static HBaseClient clientFromOptions(final ArgP argp) {
96 | if (argp.optionExists("--auto-metric") && argp.has("--auto-metric")) {
97 | System.setProperty("tsd.core.auto_create_metrics", "true");
98 | }
99 | final String zkq = argp.get("--zkquorum", "localhost");
100 | if (argp.has("--zkbasedir")) {
101 | return new HBaseClient(zkq, argp.get("--zkbasedir"));
102 | } else {
103 | return new HBaseClient(zkq);
104 | }
105 | }
106 |
107 | }
108 |
--------------------------------------------------------------------------------
/src/core/Query.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2010-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.core;
14 |
15 | import java.util.Map;
16 |
17 | import org.hbase.async.HBaseException;
18 |
19 | import net.opentsdb.uid.NoSuchUniqueName;
20 |
21 | /**
22 | * A query to retreive data from the TSDB.
23 | */
24 | public interface Query {
25 |
26 | /**
27 | * Sets the start time of the graph.
28 | * @param timestamp The start time, all the data points returned will have a
29 | * timestamp greater than or equal to this one.
30 | * @throws IllegalArgumentException if timestamp is less than or equal to 0,
31 | * or if it can't fit on 32 bits.
32 | * @throws IllegalArgumentException if
33 | * {@code timestamp >= }{@link #getEndTime getEndTime}.
34 | */
35 | void setStartTime(long timestamp);
36 |
37 | /**
38 | * Returns the start time of the graph.
39 | * @return A strictly positive integer.
40 | * @throws IllegalStateException if {@link #setStartTime} was never called on
41 | * this instance before.
42 | */
43 | long getStartTime();
44 |
45 | /**
46 | * Sets the end time of the graph.
47 | * @param timestamp The end time, all the data points returned will have a
48 | * timestamp less than or equal to this one.
49 | * @throws IllegalArgumentException if timestamp is less than or equal to 0,
50 | * or if it can't fit on 32 bits.
51 | * @throws IllegalArgumentException if
52 | * {@code timestamp <= }{@link #getStartTime getStartTime}.
53 | */
54 | void setEndTime(long timestamp);
55 |
56 | /**
57 | * Returns the end time of the graph.
58 | *
59 | * If {@link #setEndTime} was never called before, this method will
60 | * automatically execute
61 | * {@link #setEndTime setEndTime}{@code (System.currentTimeMillis() / 1000)}
62 | * to set the end time.
63 | * @return A strictly positive integer.
64 | */
65 | long getEndTime();
66 |
67 | /**
68 | * Sets the time series to the query.
69 | * @param metric The metric to retreive from the TSDB.
70 | * @param tags The set of tags of interest.
71 | * @param function The aggregation function to use.
72 | * @param rate If true, the rate of the series will be used instead of the
73 | * actual values.
74 | * @throws NoSuchUniqueName if the name of a metric, or a tag name/value
75 | * does not exist.
76 | */
77 | void setTimeSeries(String metric, Map tags,
78 | Aggregator function, boolean rate) throws NoSuchUniqueName;
79 |
80 | /**
81 | * Downsamples the results by specifying a fixed interval between points.
82 | *
83 | * Technically, downsampling means reducing the sampling interval. Here
84 | * the idea is similar. Instead of returning every single data point that
85 | * matched the query, we want one data point per fixed time interval. The
86 | * way we get this one data point is by aggregating all the data points of
87 | * that interval together using an {@link Aggregator}. This enables you
88 | * to compute things like the 5-minute average or 10 minute 99th percentile.
89 | * @param interval Number of seconds wanted between each data point.
90 | * @param downsampler Aggregation function to use to group data points
91 | * within an interval.
92 | */
93 | void downsample(int interval, Aggregator downsampler);
94 |
95 | /**
96 | * Runs this query.
97 | * @return The data points matched by this query.
98 | *
99 | * Each element in the non-{@code null} but possibly empty array returned
100 | * corresponds to one time series for which some data points have been
101 | * matched by the query.
102 | * @throws HBaseException if there was a problem communicating with HBase to
103 | * perform the search.
104 | */
105 | DataPoints[] run() throws HBaseException;
106 |
107 | }
108 |
--------------------------------------------------------------------------------
/src/core/DataPointsIterator.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2010-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.core;
14 |
15 | import java.util.NoSuchElementException;
16 |
17 | /** Default iterator for simple implementations of {@link DataPoints}. */
18 | final class DataPointsIterator implements SeekableView, DataPoint {
19 |
20 | /** Instance to iterate on. */
21 | private final DataPoints dp;
22 |
23 | /** Where are we in the iteration. */
24 | private short index = -1;
25 |
26 | /**
27 | * Ctor.
28 | * @param dp The data points to iterate on.
29 | */
30 | DataPointsIterator(final DataPoints dp) {
31 | this.dp = dp;
32 | }
33 |
34 | // ------------------ //
35 | // Iterator interface //
36 | // ------------------ //
37 |
38 | public boolean hasNext() {
39 | return index < dp.size() - 1;
40 | }
41 |
42 | public DataPoint next() {
43 | if (hasNext()) {
44 | index++;
45 | return this;
46 | }
47 | throw new NoSuchElementException("no more elements in " + this);
48 | }
49 |
50 | public void remove() {
51 | throw new UnsupportedOperationException();
52 | }
53 |
54 | // ---------------------- //
55 | // SeekableView interface //
56 | // ---------------------- //
57 |
58 | public void seek(final long timestamp) {
59 | if ((timestamp & 0xFFFFFFFF00000000L) != 0) { // negative or not 32 bits
60 | throw new IllegalArgumentException("invalid timestamp: " + timestamp);
61 | }
62 | // Do a binary search to find the timestamp given or the one right before.
63 | short lo = 0;
64 | short hi = (short) dp.size();
65 |
66 | while (lo <= hi) {
67 | index = (short) ((lo + hi) >>> 1);
68 | long cmp = dp.timestamp(index) - timestamp;
69 |
70 | if (cmp < 0) {
71 | lo = (short) (index + 1);
72 | } else if (cmp > 0) {
73 | hi = (short) (index - 1);
74 | } else {
75 | index--; // 'index' is exactly on the timestamp wanted.
76 | return; // So let's go right before that for next().
77 | }
78 | }
79 | // We found the timestamp right before as there was no exact match.
80 | // We take that position - 1 so the next call to next() returns it.
81 | index = (short) (lo - 1);
82 | // If the index we found was not the first or the last point, let's
83 | // do a small extra sanity check to ensure the position we found makes
84 | // sense: the timestamp we're at must not be >= what we're looking for.
85 | if (0 < index && index < dp.size() && dp.timestamp(index) >= timestamp) {
86 | throw new AssertionError("seeked after the time wanted!"
87 | + " timestamp=" + timestamp
88 | + ", index=" + index
89 | + ", dp.timestamp(index)=" + dp.timestamp(index)
90 | + ", this=" + this);
91 | }
92 | }
93 |
94 | /** Package-private helper to find the current index of this iterator. */
95 | int index() {
96 | return index;
97 | }
98 |
99 | // ------------------- //
100 | // DataPoint interface //
101 | // ------------------- //
102 |
103 | public long timestamp() {
104 | return dp.timestamp(index);
105 | }
106 |
107 | public boolean isInteger() {
108 | return dp.isInteger(index);
109 | }
110 |
111 | public long longValue() {
112 | return dp.longValue(index);
113 | }
114 |
115 | public double doubleValue() {
116 | return dp.doubleValue(index);
117 | }
118 |
119 | public double toDouble() {
120 | return isInteger() ? longValue() : doubleValue();
121 | }
122 |
123 | public String toString() {
124 | return "DataPointsIterator(index=" + index
125 | + (index >= 0
126 | ? ", current type: " + (isInteger() ? "long" : "float")
127 | + ", current value=" + (isInteger() ? longValue() : doubleValue())
128 | : " (iteration not started)")
129 | + ", dp=" + dp + ')';
130 | }
131 |
132 | }
133 |
--------------------------------------------------------------------------------
/test/core/TestAggregators.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.core;
14 |
15 | import java.util.Random;
16 |
17 | import org.junit.Assert;
18 | import org.junit.Test;
19 |
20 | public final class TestAggregators {
21 |
22 | private static final Random random;
23 | static {
24 | final long seed = System.nanoTime();
25 | System.out.println("Random seed: " + seed);
26 | random = new Random(seed);
27 | }
28 |
29 | /**
30 | * Epsilon used to compare floating point values.
31 | * Instead of using a fixed epsilon to compare our numbers, we calculate
32 | * it based on the percentage of our actual expected values. We do things
33 | * this way because our numbers can be extremely large and if you change
34 | * the scale of the numbers a static precision may no longer work
35 | */
36 | private static final double EPSILON_PERCENTAGE = 0.0001;
37 |
38 | /** Helper class to hold a bunch of numbers we can iterate on. */
39 | private static final class Numbers implements Aggregator.Longs, Aggregator.Doubles {
40 | private final long[] numbers;
41 | private int i = 0;
42 |
43 | public Numbers(final long[] numbers) {
44 | this.numbers = numbers;
45 | }
46 |
47 | @Override
48 | public boolean hasNextValue() {
49 | return i < numbers.length;
50 | }
51 |
52 | @Override
53 | public long nextLongValue() {
54 | return numbers[i++];
55 | }
56 |
57 | @Override
58 | public double nextDoubleValue() {
59 | return numbers[i++];
60 | }
61 |
62 | void reset() {
63 | i = 0;
64 | }
65 | }
66 |
67 | @Test
68 | public void testStdDevKnownValues() {
69 | final long[] values = new long[10000];
70 | for (int i = 0; i < values.length; i++) {
71 | values[i] = i;
72 | }
73 | // Expected value calculated by NumPy
74 | // $ python2.7
75 | // >>> import numpy
76 | // >>> numpy.std(range(10000))
77 | // 2886.7513315143719
78 | final double expected = 2886.7513315143719D;
79 | final double epsilon = 0.01;
80 | checkSimilarStdDev(values, expected, epsilon);
81 | }
82 |
83 | @Test
84 | public void testStdDevRandomValues() {
85 | final long[] values = new long[1000];
86 | for (int i = 0; i < values.length; i++) {
87 | values[i] = random.nextLong();
88 | }
89 | final double expected = naiveStdDev(values);
90 | // Calculate the epsilon based on the percentage of the number.
91 | final double epsilon = EPSILON_PERCENTAGE * expected;
92 | checkSimilarStdDev(values, expected, epsilon);
93 | }
94 |
95 | @Test
96 | public void testStdDevNoDeviation() {
97 | final long[] values = {3,3,3};
98 |
99 | final double expected = 0;
100 | checkSimilarStdDev(values, expected, 0);
101 | }
102 |
103 | @Test
104 | public void testStdDevFewDataInputs() {
105 | final long[] values = {1,2};
106 |
107 | final double expected = 0.5;
108 | checkSimilarStdDev(values, expected, 0);
109 | }
110 |
111 | private static void checkSimilarStdDev(final long[] values,
112 | final double expected,
113 | final double epsilon) {
114 | final Numbers numbers = new Numbers(values);
115 | final Aggregator agg = Aggregators.get("dev");
116 |
117 | Assert.assertEquals(expected, agg.runDouble(numbers), epsilon);
118 | numbers.reset();
119 | Assert.assertEquals(expected, agg.runLong(numbers), Math.max(epsilon, 1.0));
120 | }
121 |
122 | private static double naiveStdDev(long[] values) {
123 | double sum = 0;
124 | for (final double value : values) {
125 | sum += value;
126 | }
127 | double mean = sum / values.length;
128 |
129 | double squaresum = 0;
130 | for (final double value : values) {
131 | squaresum += Math.pow(value - mean, 2);
132 | }
133 | final double variance = squaresum / values.length;
134 | return Math.sqrt(variance);
135 | }
136 |
137 | }
138 |
--------------------------------------------------------------------------------
/src/tsd/LogsRpc.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2010-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.tsd;
14 |
15 | import org.slf4j.LoggerFactory;
16 |
17 | import java.util.Iterator;
18 | import java.util.NoSuchElementException;
19 |
20 | import ch.qos.logback.classic.Level;
21 | import ch.qos.logback.classic.Logger;
22 | import ch.qos.logback.classic.spi.ILoggingEvent;
23 | import ch.qos.logback.classic.spi.IThrowableProxy;
24 | import ch.qos.logback.classic.spi.ThrowableProxyUtil;
25 | import ch.qos.logback.core.read.CyclicBufferAppender;
26 |
27 | import net.opentsdb.core.TSDB;
28 |
29 | /** The "/logs" endpoint. */
30 | final class LogsRpc implements HttpRpc {
31 |
32 | public void execute(final TSDB tsdb, final HttpQuery query) {
33 | LogIterator logmsgs = new LogIterator();
34 | if (query.hasQueryStringParam("json")) {
35 | query.sendJsonArray(logmsgs);
36 | } else if (query.hasQueryStringParam("level")) {
37 | final Level level = Level.toLevel(query.getQueryStringParam("level"),
38 | null);
39 | if (level == null) {
40 | throw new BadRequestException("Invalid level: "
41 | + query.getQueryStringParam("level"));
42 | }
43 | final Logger root =
44 | (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
45 | String logger_name = query.getQueryStringParam("logger");
46 | if (logger_name == null) {
47 | logger_name = Logger.ROOT_LOGGER_NAME;
48 | } else if (root.getLoggerContext().exists(logger_name) == null) {
49 | throw new BadRequestException("Invalid logger: " + logger_name);
50 | }
51 | final Logger logger = (Logger) LoggerFactory.getLogger(logger_name);
52 | int nloggers = 0;
53 | if (logger == root) { // Update all the loggers.
54 | for (final Logger l : logger.getLoggerContext().getLoggerList()) {
55 | l.setLevel(level);
56 | nloggers++;
57 | }
58 | } else {
59 | logger.setLevel(level);
60 | nloggers++;
61 | }
62 | query.sendReply("Set the log level to " + level + " on " + nloggers
63 | + " logger" + (nloggers > 1 ? "s" : "") + ".\n");
64 | } else {
65 | final StringBuilder buf = new StringBuilder(512);
66 | for (final String logmsg : logmsgs) {
67 | buf.append(logmsg).append('\n');
68 | }
69 | logmsgs = null;
70 | query.sendReply(buf);
71 | }
72 | }
73 |
74 | /** Helper class to iterate over logback's recent log messages. */
75 | private static final class LogIterator implements Iterator,
76 | Iterable {
77 |
78 | private final CyclicBufferAppender logbuf;
79 | private final StringBuilder buf = new StringBuilder(64);
80 | private int nevents;
81 |
82 | public LogIterator() {
83 | final Logger root =
84 | (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
85 | logbuf = (CyclicBufferAppender) root.getAppender("CYCLIC");
86 | }
87 |
88 | public Iterator iterator() {
89 | nevents = logbuf.getLength();
90 | return this;
91 | }
92 |
93 | public boolean hasNext() {
94 | return nevents > 0;
95 | }
96 |
97 | public String next() {
98 | if (hasNext()) {
99 | nevents--;
100 | final ILoggingEvent event = (ILoggingEvent) logbuf.get(nevents);
101 | final String msg = event.getFormattedMessage();
102 | buf.setLength(0);
103 | buf.append(event.getTimeStamp() / 1000)
104 | .append('\t').append(event.getLevel().toString())
105 | .append('\t').append(event.getThreadName())
106 | .append('\t').append(event.getLoggerName())
107 | .append('\t').append(msg);
108 | final IThrowableProxy thrown = event.getThrowableProxy();
109 | if (thrown != null) {
110 | buf.append('\t').append(ThrowableProxyUtil.asString(thrown));
111 | }
112 | return buf.toString();
113 | }
114 | throw new NoSuchElementException("no more elements");
115 | }
116 |
117 | public void remove() {
118 | throw new UnsupportedOperationException();
119 | }
120 |
121 | }
122 |
123 | }
124 |
--------------------------------------------------------------------------------
/test/core/TestTags.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2011-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.core;
14 |
15 | import java.util.HashMap;
16 |
17 | import org.junit.Test;
18 | import static org.junit.Assert.assertEquals;
19 |
20 | public final class TestTags {
21 |
22 | @Test
23 | public void parseSuccessful() {
24 | final HashMap tags = new HashMap(2);
25 | Tags.parse(tags, "foo=bar");
26 | assertEquals(1, tags.size());
27 | assertEquals("bar", tags.get("foo"));
28 | Tags.parse(tags, "qux=baz");
29 | assertEquals(2, tags.size());
30 | assertEquals("bar", tags.get("foo"));
31 | assertEquals("baz", tags.get("qux"));
32 | }
33 |
34 | @Test(expected=IllegalArgumentException.class)
35 | public void parseNoEqualSign() {
36 | Tags.parse(new HashMap(1), "foo");
37 | }
38 |
39 | @Test(expected=IllegalArgumentException.class)
40 | public void parseTooManyEqualSigns() {
41 | Tags.parse(new HashMap(1), "foo=bar=qux");
42 | }
43 |
44 | @Test(expected=IllegalArgumentException.class)
45 | public void parseEmptyTagName() {
46 | Tags.parse(new HashMap(1), "=bar");
47 | }
48 |
49 | @Test(expected=IllegalArgumentException.class)
50 | public void parseEmptyTagValue() {
51 | Tags.parse(new HashMap(1), "foo=");
52 | }
53 |
54 | @Test(expected=IllegalArgumentException.class)
55 | public void parseDifferentValues() {
56 | final HashMap tags = new HashMap(1);
57 | Tags.parse(tags, "foo=bar");
58 | assertEquals(1, tags.size());
59 | assertEquals("bar", tags.get("foo"));
60 | Tags.parse(tags, "foo=qux");
61 | }
62 |
63 | @Test
64 | public void parseSameValues() {
65 | final HashMap tags = new HashMap(1);
66 | Tags.parse(tags, "foo=bar");
67 | assertEquals(1, tags.size());
68 | assertEquals("bar", tags.get("foo"));
69 | Tags.parse(tags, "foo=bar");
70 | assertEquals(1, tags.size());
71 | assertEquals("bar", tags.get("foo"));
72 | }
73 |
74 | @Test
75 | public void validateGoodString() {
76 | Tags.validateString("test", "omg-TSDB/42._foo_");
77 | }
78 |
79 | @Test(expected=IllegalArgumentException.class)
80 | public void validateNullString() {
81 | Tags.validateString("test", null);
82 | }
83 |
84 | @Test(expected=IllegalArgumentException.class)
85 | public void validateBadString() {
86 | Tags.validateString("test", "this is a test!");
87 | }
88 |
89 | // parseLong
90 |
91 | @Test
92 | public void parseLongSimple() {
93 | assertEquals(0, Tags.parseLong("0"));
94 | assertEquals(0, Tags.parseLong("+0"));
95 | assertEquals(0, Tags.parseLong("-0"));
96 | assertEquals(1, Tags.parseLong("1"));
97 | assertEquals(1, Tags.parseLong("+1"));
98 | assertEquals(-1, Tags.parseLong("-1"));
99 | assertEquals(4242, Tags.parseLong("4242"));
100 | assertEquals(4242, Tags.parseLong("+4242"));
101 | assertEquals(-4242, Tags.parseLong("-4242"));
102 | }
103 |
104 | @Test
105 | public void parseLongMaxValue() {
106 | assertEquals(Long.MAX_VALUE, Tags.parseLong(Long.toString(Long.MAX_VALUE)));
107 | }
108 |
109 | @Test
110 | public void parseLongMinValue() {
111 | assertEquals(Long.MIN_VALUE, Tags.parseLong(Long.toString(Long.MIN_VALUE)));
112 | }
113 |
114 | @Test(expected=NumberFormatException.class)
115 | public void parseLongEmptyString() {
116 | Tags.parseLong("");
117 | }
118 |
119 | @Test(expected=NumberFormatException.class)
120 | public void parseLongMalformed() {
121 | Tags.parseLong("42a51");
122 | }
123 |
124 | @Test(expected=NumberFormatException.class)
125 | public void parseLongMalformedPlus() {
126 | Tags.parseLong("+");
127 | }
128 |
129 | @Test(expected=NumberFormatException.class)
130 | public void parseLongMalformedMinus() {
131 | Tags.parseLong("-");
132 | }
133 |
134 | @Test(expected=NumberFormatException.class)
135 | public void parseLongValueTooLarge() {
136 | Tags.parseLong("18446744073709551616");
137 | }
138 |
139 | @Test(expected=NumberFormatException.class)
140 | public void parseLongValueTooLargeSubtle() {
141 | Tags.parseLong("9223372036854775808"); // MAX_VALUE + 1
142 | }
143 |
144 | @Test(expected=NumberFormatException.class)
145 | public void parseLongValueTooSmallSubtle() {
146 | Tags.parseLong("-9223372036854775809"); // MIN_VALUE - 1
147 | }
148 |
149 | }
150 |
--------------------------------------------------------------------------------
/src/core/Internal.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2011-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.core;
14 |
15 | import java.util.ArrayList;
16 | import java.util.Map;
17 |
18 | import org.hbase.async.Bytes;
19 | import org.hbase.async.KeyValue;
20 | import org.hbase.async.Scanner;
21 |
22 | /**
23 | * This class is not part of the public API.
24 | *
25 | * ,____________________________,
26 | * | This class is reserved for |
27 | * | OpenTSDB's internal usage! |
28 | * `----------------------------'
29 | * \ / \ //\
30 | * \ |\___/| / \// \\
31 | * /0 0 \__ / // | \ \
32 | * / / \/_/ // | \ \
33 | * @_^_@'/ \/_ // | \ \
34 | * //_^_/ \/_ // | \ \
35 | * ( //) | \/// | \ \
36 | * ( / /) _|_ / ) // | \ _\
37 | * ( // /) '/,_ _ _/ ( ; -. | _ _\.-~ .-~~~^-.
38 | * (( / / )) ,-{ _ `-.|.-~-. .~ `.
39 | * (( // / )) '/\ / ~-. _ .-~ .-~^-. \
40 | * (( /// )) `. { } / \ \
41 | * (( / )) .----~-.\ \-' .~ \ `. \^-.
42 | * ///.----../ \ _ -~ `. ^-` ^-_
43 | * ///-._ _ _ _ _ _ _}^ - - - - ~ ~-- ,.-~
44 | * /.-~
45 | * You've been warned by the dragon!
46 | *
47 | * This class is reserved for OpenTSDB's own internal usage only. If you use
48 | * anything from this package outside of OpenTSDB, a dragon will spontaneously
49 | * appear and eat you. You've been warned.
50 | *
51 | * This class only exists because Java's packaging system is annoying as the
52 | * "package-private" accessibility level only applies to the current package
53 | * but not its sub-packages, and because Java doesn't have fine-grained API
54 | * visibility mechanism such as that of Scala or C++.
55 | *
56 | * This package provides access into internal methods for higher-level
57 | * packages, for the sake of reducing code duplication and (ab)use of
58 | * reflection.
59 | */
60 | public final class Internal {
61 |
62 | /** @see Const#FLAG_BITS */
63 | public static final short FLAG_BITS = Const.FLAG_BITS;
64 |
65 | /** @see Const#LENGTH_MASK */
66 | public static final short LENGTH_MASK = Const.LENGTH_MASK;
67 |
68 | /** @see Const#FLAGS_MASK */
69 | public static final short FLAGS_MASK = Const.FLAGS_MASK;
70 |
71 | private Internal() {
72 | // Can't instantiate.
73 | }
74 |
75 | /** @see TsdbQuery#getScanner */
76 | public static Scanner getScanner(final Query query) {
77 | return ((TsdbQuery) query).getScanner();
78 | }
79 |
80 | /** @see RowKey#metricName */
81 | public static String metricName(final TSDB tsdb, final byte[] id) {
82 | return RowKey.metricName(tsdb, id);
83 | }
84 |
85 | /** Extracts the timestamp from a row key. */
86 | public static long baseTime(final TSDB tsdb, final byte[] row) {
87 | return Bytes.getUnsignedInt(row, tsdb.metrics.width());
88 | }
89 |
90 | /** @see Tags#getTags */
91 | public static Map getTags(final TSDB tsdb, final byte[] row) {
92 | return Tags.getTags(tsdb, row);
93 | }
94 |
95 | /** @see RowSeq#extractIntegerValue */
96 | public static long extractIntegerValue(final byte[] values,
97 | final int value_idx,
98 | final byte flags) {
99 | return RowSeq.extractIntegerValue(values, value_idx, flags);
100 | }
101 |
102 | /** @see RowSeq#extractFloatingPointValue */
103 | public static double extractFloatingPointValue(final byte[] values,
104 | final int value_idx,
105 | final byte flags) {
106 | return RowSeq.extractFloatingPointValue(values, value_idx, flags);
107 | }
108 |
109 | public static short metricWidth(final TSDB tsdb) {
110 | return tsdb.metrics.width();
111 | }
112 |
113 | /** @see CompactionQueue#complexCompact */
114 | public static KeyValue complexCompact(final KeyValue kv) {
115 | final ArrayList kvs = new ArrayList(1);
116 | kvs.add(kv);
117 | return CompactionQueue.complexCompact(kvs, kv.qualifier().length / 2);
118 | }
119 |
120 | }
121 |
--------------------------------------------------------------------------------
/src/tsd/PutDataPointRpc.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2010-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.tsd;
14 |
15 | import java.util.HashMap;
16 | import java.util.concurrent.atomic.AtomicLong;
17 |
18 | import com.stumbleupon.async.Callback;
19 | import com.stumbleupon.async.Deferred;
20 |
21 | import org.jboss.netty.channel.Channel;
22 |
23 | import net.opentsdb.core.TSDB;
24 | import net.opentsdb.core.Tags;
25 | import net.opentsdb.stats.StatsCollector;
26 | import net.opentsdb.uid.NoSuchUniqueName;
27 |
28 | /** Implements the "put" telnet-style command. */
29 | final class PutDataPointRpc implements TelnetRpc {
30 |
31 | private static final AtomicLong requests = new AtomicLong();
32 | private static final AtomicLong hbase_errors = new AtomicLong();
33 | private static final AtomicLong invalid_values = new AtomicLong();
34 | private static final AtomicLong illegal_arguments = new AtomicLong();
35 | private static final AtomicLong unknown_metrics = new AtomicLong();
36 |
37 | public Deferred execute(final TSDB tsdb, final Channel chan,
38 | final String[] cmd) {
39 | requests.incrementAndGet();
40 | String errmsg = null;
41 | try {
42 | final class PutErrback implements Callback {
43 | public Exception call(final Exception arg) {
44 | if (chan.isConnected()) {
45 | chan.write("put: HBase error: " + arg.getMessage() + '\n');
46 | }
47 | hbase_errors.incrementAndGet();
48 | return arg;
49 | }
50 | public String toString() {
51 | return "report error to channel";
52 | }
53 | }
54 | return importDataPoint(tsdb, cmd).addErrback(new PutErrback());
55 | } catch (NumberFormatException x) {
56 | errmsg = "put: invalid value: " + x.getMessage() + '\n';
57 | invalid_values.incrementAndGet();
58 | } catch (IllegalArgumentException x) {
59 | errmsg = "put: illegal argument: " + x.getMessage() + '\n';
60 | illegal_arguments.incrementAndGet();
61 | } catch (NoSuchUniqueName x) {
62 | errmsg = "put: unknown metric: " + x.getMessage() + '\n';
63 | unknown_metrics.incrementAndGet();
64 | }
65 | if (errmsg != null && chan.isConnected()) {
66 | chan.write(errmsg);
67 | }
68 | return Deferred.fromResult(null);
69 | }
70 |
71 | /**
72 | * Collects the stats and metrics tracked by this instance.
73 | * @param collector The collector to use.
74 | */
75 | public static void collectStats(final StatsCollector collector) {
76 | collector.record("rpc.received", requests, "type=put");
77 | collector.record("rpc.errors", hbase_errors, "type=hbase_errors");
78 | collector.record("rpc.errors", invalid_values, "type=invalid_values");
79 | collector.record("rpc.errors", illegal_arguments, "type=illegal_arguments");
80 | collector.record("rpc.errors", unknown_metrics, "type=unknown_metrics");
81 | }
82 |
83 | /**
84 | * Imports a single data point.
85 | * @param tsdb The TSDB to import the data point into.
86 | * @param words The words describing the data point to import, in
87 | * the following format: {@code [metric, timestamp, value, ..tags..]}
88 | * @return A deferred object that indicates the completion of the request.
89 | * @throws NumberFormatException if the timestamp or value is invalid.
90 | * @throws IllegalArgumentException if any other argument is invalid.
91 | * @throws NoSuchUniqueName if the metric isn't registered.
92 | */
93 | private Deferred importDataPoint(final TSDB tsdb, final String[] words) {
94 | words[0] = null; // Ditch the "put".
95 | if (words.length < 5) { // Need at least: metric timestamp value tag
96 | // ^ 5 and not 4 because words[0] is "put".
97 | throw new IllegalArgumentException("not enough arguments"
98 | + " (need least 4, got " + (words.length - 1) + ')');
99 | }
100 | final String metric = words[1];
101 | if (metric.length() <= 0) {
102 | throw new IllegalArgumentException("empty metric name");
103 | }
104 | final long timestamp = Tags.parseLong(words[2]);
105 | if (timestamp <= 0) {
106 | throw new IllegalArgumentException("invalid timestamp: " + timestamp);
107 | }
108 | final String value = words[3];
109 | if (value.length() <= 0) {
110 | throw new IllegalArgumentException("empty value");
111 | }
112 | final HashMap tags = new HashMap();
113 | for (int i = 4; i < words.length; i++) {
114 | if (!words[i].isEmpty()) {
115 | Tags.parse(tags, words[i]);
116 | }
117 | }
118 | if (Tags.looksLikeInteger(value)) {
119 | return tsdb.addPoint(metric, timestamp, Tags.parseLong(value), tags);
120 | } else { // floating point value
121 | return tsdb.addPoint(metric, timestamp, Float.parseFloat(value), tags);
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/core/WritableDataPoints.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2010-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.core;
14 |
15 | import java.util.Map;
16 |
17 | import com.stumbleupon.async.Deferred;
18 |
19 | import org.hbase.async.HBaseException;
20 |
21 | /**
22 | * Represents a mutable sequence of continuous data points.
23 | *
24 | * Implementations of this interface aren't expected to be synchronized.
25 | */
26 | public interface WritableDataPoints extends DataPoints {
27 |
28 | /**
29 | * Sets the metric name and tags of the series.
30 | *
31 | * This method can be called multiple times on the same instance to start
32 | * adding data points to another time series without having to create a new
33 | * instance.
34 | * @param metric A non-empty string.
35 | * @param tags The tags on this series. This map must be non-empty.
36 | * @throws IllegalArgumentException if the metric name is empty or contains
37 | * illegal characters.
38 | * @throws IllegalArgumentException if the tags list is empty or one of the
39 | * elements contains illegal characters.
40 | */
41 | void setSeries(String metric, Map tags);
42 |
43 | /**
44 | * Adds a {@code long} data point to the TSDB.
45 | *
46 | * The data point is immediately persisted unless {@link #setBufferingTime}
47 | * is used. Data points must be added in chronological order.
48 | * @param timestamp The timestamp associated with the value.
49 | * @param value The value of the data point.
50 | * @return A deferred object that indicates the completion of the request.
51 | * The {@link Object} has not special meaning and can be {@code null} (think
52 | * of it as {@code Deferred}). But you probably want to attach at
53 | * least an errback to this {@code Deferred} to handle failures.
54 | * @throws IllegalArgumentException if the timestamp is less than or equal
55 | * to the previous timestamp added or 0 for the first timestamp, or if the
56 | * difference with the previous timestamp is too large.
57 | * @throws HBaseException (deferred) if there was a problem while persisting
58 | * data.
59 | */
60 | Deferred addPoint(long timestamp, long value);
61 |
62 | /**
63 | * Appends a {@code float} data point to this sequence.
64 | *
65 | * The data point is immediately persisted unless {@link #setBufferingTime}
66 | * is used. Data points must be added in chronological order.
67 | * @param timestamp The timestamp associated with the value.
68 | * @param value The value of the data point.
69 | * @return A deferred object that indicates the completion of the request.
70 | * The {@link Object} has not special meaning and can be {@code null} (think
71 | * of it as {@code Deferred}). But you probably want to attach at
72 | * least an errback to this {@code Deferred} to handle failures.
73 | * @throws IllegalArgumentException if the timestamp is less than or equal
74 | * to the previous timestamp added or 0 for the first timestamp, or if the
75 | * difference with the previous timestamp is too large.
76 | * @throws IllegalArgumentException if the value is {@code NaN} or
77 | * {@code Infinite}.
78 | * @throws HBaseException (deferred) if there was a problem while persisting
79 | * data.
80 | */
81 | Deferred addPoint(long timestamp, float value);
82 |
83 | /**
84 | * Specifies for how long to buffer edits, in milliseconds.
85 | *
86 | * By calling this method, you're allowing new data points to be buffered
87 | * before being sent to HBase. {@code 0} (the default) means data points
88 | * are persisted immediately.
89 | *
90 | * Buffering improves performance, reduces the number of RPCs sent to HBase,
91 | * but can cause data loss if we die before we get a chance to send buffered
92 | * edits to HBase. It also entails that buffered data points aren't visible
93 | * to other applications using the TSDB until they're flushed to HBase.
94 | * @param time The approximate maximum number of milliseconds for which data
95 | * points should be buffered before being sent to HBase. This deadline will
96 | * be honored on a "best effort" basis.
97 | */
98 | void setBufferingTime(short time);
99 |
100 | /**
101 | * Specifies whether or not this is a batch import.
102 | *
103 | * It is preferred that this method be called for anything importing a batch
104 | * of data points (as opposed to streaming in new data points in real time).
105 | *
106 | * Calling this method changes a few important things:
107 | *
108 | * Data points may not be persisted immediately. In the event of an
109 | * outage in HBase during or slightly after the import, un-persisted data
110 | * points will be lost.
111 | * {@link #setBufferingTime} may be called with an argument
112 | * chosen by the implementation.
113 | *
114 | * @param batchornot if true, then this is a batch import.
115 | */
116 | void setBatchImport(boolean batchornot);
117 |
118 | }
119 |
--------------------------------------------------------------------------------
/test/stats/TestHistogram.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2010-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.stats;
14 |
15 | import junit.framework.TestCase;
16 |
17 | public final class TestHistogram extends TestCase {
18 |
19 | public void test_percentile_empty_histogram() {
20 | final Histogram histo = new Histogram(16000, (short) 2, 100);
21 | assertEquals(0, histo.percentile(1));
22 | assertEquals(0, histo.percentile(50));
23 | assertEquals(0, histo.percentile(99));
24 | }
25 |
26 | public void test_16Max_1Interval_5Cutoff() {
27 | final Histogram histo = new Histogram(16, (short) 1, 5);
28 | assertEquals(10, histo.buckets());
29 |
30 | histo.add(4);
31 | assertBucketEquals(histo, 4, 1);
32 |
33 | histo.add(5);
34 | assertBucketEquals(histo, 5, 1);
35 |
36 | histo.add(5);
37 | assertBucketEquals(histo, 5, 2);
38 |
39 | histo.add(0);
40 | assertBucketEquals(histo, 0, 1);
41 |
42 | histo.add(42);
43 | assertBucketEquals(histo, 9, 1);
44 |
45 | histo.add(6);
46 | assertBucketEquals(histo, 5, 3);
47 |
48 | histo.add(9);
49 | assertBucketEquals(histo, 7, 1);
50 |
51 | histo.add(10);
52 | assertBucketEquals(histo, 7, 2);
53 |
54 | assertBucketEquals(histo, 0, 1);
55 | assertBucketEquals(histo, 1, 0);
56 | assertBucketEquals(histo, 2, 0);
57 | assertBucketEquals(histo, 3, 0);
58 | assertBucketEquals(histo, 4, 1);
59 | assertBucketEquals(histo, 5, 3);
60 | assertBucketEquals(histo, 6, 0);
61 | assertBucketEquals(histo, 7, 2);
62 | assertBucketEquals(histo, 8, 0);
63 | assertBucketEquals(histo, 9, 1);
64 | }
65 |
66 | public void test_16Max_2Interval_5Cutoff() {
67 | final Histogram histo = new Histogram(16, (short) 2, 5);
68 | assertEquals(6, histo.buckets());
69 |
70 | histo.add(4);
71 | assertBucketEquals(histo, 2, 1);
72 |
73 | histo.add(6);
74 | assertBucketEquals(histo, 2, 2);
75 |
76 | histo.add(7);
77 | assertBucketEquals(histo, 2, 3);
78 |
79 | histo.add(0);
80 | assertBucketEquals(histo, 0, 1);
81 |
82 | histo.add(42);
83 | assertBucketEquals(histo, 5, 1);
84 |
85 | histo.add(8);
86 | assertBucketEquals(histo, 3, 1);
87 |
88 | histo.add(9);
89 | assertBucketEquals(histo, 3, 2);
90 |
91 | histo.add(10);
92 | assertBucketEquals(histo, 3, 3);
93 |
94 | histo.add(11);
95 | assertBucketEquals(histo, 3, 4);
96 |
97 | histo.add(12);
98 | assertBucketEquals(histo, 5, 1);
99 |
100 | assertBucketEquals(histo, 0, 1);
101 | assertBucketEquals(histo, 1, 0);
102 | assertBucketEquals(histo, 2, 3);
103 | assertBucketEquals(histo, 3, 4);
104 | assertBucketEquals(histo, 4, 1);
105 | assertBucketEquals(histo, 5, 1);
106 | }
107 |
108 | public void test_160Max_20Interval_50Cutoff() {
109 | final Histogram histo = new Histogram(160, (short) 20, 50);
110 | assertEquals(6, histo.buckets());
111 |
112 | histo.add(0);
113 | assertBucketEquals(histo, 0, 1);
114 |
115 | histo.add(40);
116 | assertBucketEquals(histo, 2, 1);
117 |
118 | histo.add(50);
119 | assertBucketEquals(histo, 2, 2);
120 |
121 | histo.add(60);
122 | assertBucketEquals(histo, 2, 3);
123 |
124 | histo.add(71);
125 | assertBucketEquals(histo, 2, 4);
126 |
127 | histo.add(72);
128 | assertBucketEquals(histo, 3, 1);
129 |
130 | histo.add(103);
131 | assertBucketEquals(histo, 3, 2);
132 |
133 | histo.add(104);
134 | assertBucketEquals(histo, 4, 1);
135 |
136 | histo.add(130);
137 | assertBucketEquals(histo, 4, 2);
138 |
139 | histo.add(160);
140 | assertBucketEquals(histo, 4, 3);
141 |
142 | histo.add(167);
143 | assertBucketEquals(histo, 4, 4);
144 |
145 | histo.add(168);
146 | assertBucketEquals(histo, 5, 1);
147 |
148 | histo.add(420);
149 | assertBucketEquals(histo, 5, 2);
150 |
151 | assertBucketEquals(histo, 0, 1);
152 | assertBucketEquals(histo, 1, 0);
153 | assertBucketEquals(histo, 2, 4);
154 | assertBucketEquals(histo, 3, 2);
155 | assertBucketEquals(histo, 4, 4);
156 | assertBucketEquals(histo, 5, 2);
157 | }
158 |
159 | static void assertBucketEquals(final Histogram histo,
160 | final int bucket, final int expected) {
161 | int actual = histo.valueInBucket(bucket);
162 | if (actual != expected) {
163 | final StringBuilder buf = new StringBuilder();
164 | final int nbuckets = histo.buckets();
165 | for (int i = 0; i < nbuckets; i++) {
166 | histo.printAsciiBucket(buf, i);
167 | if (i == bucket) {
168 | buf.setCharAt(buf.length() - 1, ' ');
169 | buf.append(" <=== should have been ").append(expected).append('\n');
170 | }
171 | }
172 | fail("Bucket #" + bucket + " contains " + actual + " instead of "
173 | + expected + "\nHistogram:\n" + buf);
174 | }
175 | }
176 |
177 | static void printHisto(final Histogram histo) {
178 | final StringBuilder buf = new StringBuilder();
179 | histo.printAscii(buf);
180 | System.err.println(buf);
181 | }
182 |
183 | }
184 |
--------------------------------------------------------------------------------
/src/core/DataPoints.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2010-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.core;
14 |
15 | import java.util.List;
16 | import java.util.Map;
17 |
18 | /**
19 | * Represents a read-only sequence of continuous data points.
20 | *
21 | * Implementations of this interface aren't expected to be synchronized.
22 | */
23 | public interface DataPoints extends Iterable {
24 |
25 | /**
26 | * Returns the name of the series.
27 | */
28 | String metricName();
29 |
30 | /**
31 | * Returns the tags associated with these data points.
32 | * @return A non-{@code null} map of tag names (keys), tag values (values).
33 | */
34 | Map getTags();
35 |
36 | /**
37 | * Returns the tags associated with some but not all of the data points.
38 | *
39 | * When this instance represents the aggregation of multiple time series
40 | * (same metric but different tags), {@link #getTags} returns the tags that
41 | * are common to all data points (intersection set) whereas this method
42 | * returns all the tags names that are not common to all data points (union
43 | * set minus the intersection set, also called the symmetric difference).
44 | *
45 | * If this instance does not represent an aggregation of multiple time
46 | * series, the list returned is empty.
47 | * @return A non-{@code null} list of tag names.
48 | */
49 | List getAggregatedTags();
50 |
51 | /**
52 | * Returns the number of data points.
53 | *
54 | * This method must be implemented in {@code O(1)} or {@code O(n)}
55 | * where n = {@link #aggregatedSize} > 0.
56 | * @return A positive integer.
57 | */
58 | int size();
59 |
60 | /**
61 | * Returns the number of data points aggregated in this instance.
62 | *
63 | * When this instance represents the aggregation of multiple time series
64 | * (same metric but different tags), {@link #size} returns the number of data
65 | * points after aggregation, whereas this method returns the number of data
66 | * points before aggregation.
67 | *
68 | * If this instance does not represent an aggregation of multiple time
69 | * series, then 0 is returned.
70 | * @return A positive integer.
71 | */
72 | int aggregatedSize();
73 |
74 | /**
75 | * Returns a zero-copy view to go through {@code size()} data points.
76 | *
77 | * The iterator returned must return each {@link DataPoint} in {@code O(1)}.
78 | * The {@link DataPoint} returned must not be stored and gets
79 | * invalidated as soon as {@code next} is called on the iterator. If you
80 | * want to store individual data points, you need to copy the timestamp
81 | * and value out of each {@link DataPoint} into your own data structures.
82 | */
83 | SeekableView iterator();
84 |
85 | /**
86 | * Returns the timestamp associated with the {@code i}th data point.
87 | * The first data point has index 0.
88 | *
89 | * This method must be implemented in
90 | * O({@link #aggregatedSize}) or better.
91 | *
92 | * It is guaranteed that
timestamp(i) < timestamp(i+1)
93 | * @return A strictly positive integer.
94 | * @throws IndexOutOfBoundsException if {@code i} is not in the range
95 | * [0, {@link #size} - 1]
96 | */
97 | long timestamp(int i);
98 |
99 | /**
100 | * Tells whether or not the {@code i}th value is of integer type.
101 | * The first data point has index 0.
102 | *
103 | * This method must be implemented in
104 | * O({@link #aggregatedSize}) or better.
105 | * @return {@code true} if the {@code i}th value is of integer type,
106 | * {@code false} if it's of floating point type.
107 | * @throws IndexOutOfBoundsException if {@code i} is not in the range
108 | * [0, {@link #size} - 1]
109 | */
110 | boolean isInteger(int i);
111 |
112 | /**
113 | * Returns the value of the {@code i}th data point as a long.
114 | * The first data point has index 0.
115 | *
116 | * This method must be implemented in
117 | * O({@link #aggregatedSize}) or better.
118 | * Use {@link #iterator} to get successive {@code O(1)} accesses.
119 | * @see #iterator
120 | * @throws IndexOutOfBoundsException if {@code i} is not in the range
121 | * [0, {@link #size} - 1]
122 | * @throws ClassCastException if the
123 | * {@link #isInteger isInteger(i)} == false.
124 | */
125 | long longValue(int i);
126 |
127 | /**
128 | * Returns the value of the {@code i}th data point as a float.
129 | * The first data point has index 0.
130 | *
131 | * This method must be implemented in
132 | * O({@link #aggregatedSize}) or better.
133 | * Use {@link #iterator} to get successive {@code O(1)} accesses.
134 | * @see #iterator
135 | * @throws IndexOutOfBoundsException if {@code i} is not in the range
136 | * [0, {@link #size} - 1]
137 | * @throws ClassCastException if the
138 | * {@link #isInteger isInteger(i)} == true.
139 | */
140 | double doubleValue(int i);
141 |
142 | }
143 |
--------------------------------------------------------------------------------
/src/core/Aggregators.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2010-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.core;
14 |
15 | import java.util.HashMap;
16 | import java.util.NoSuchElementException;
17 | import java.util.Set;
18 |
19 | /**
20 | * Utility class that provides common, generally useful aggregators.
21 | */
22 | public final class Aggregators {
23 |
24 | /** Aggregator that sums up all the data points. */
25 | public static final Aggregator SUM = new Sum();
26 |
27 | /** Aggregator that returns the minimum data point. */
28 | public static final Aggregator MIN = new Min();
29 |
30 | /** Aggregator that returns the maximum data point. */
31 | public static final Aggregator MAX = new Max();
32 |
33 | /** Aggregator that returns the average value of the data point. */
34 | public static final Aggregator AVG = new Avg();
35 |
36 | /** Aggregator that returns the Standard Deviation of the data points. */
37 | public static final Aggregator DEV = new StdDev();
38 |
39 | /** Maps an aggregator name to its instance. */
40 | private static final HashMap aggregators;
41 |
42 | static {
43 | aggregators = new HashMap(5);
44 | aggregators.put("sum", SUM);
45 | aggregators.put("min", MIN);
46 | aggregators.put("max", MAX);
47 | aggregators.put("avg", AVG);
48 | aggregators.put("dev", DEV);
49 | }
50 |
51 | private Aggregators() {
52 | // Can't create instances of this utility class.
53 | }
54 |
55 | /**
56 | * Returns the set of the names that can be used with {@link #get get}.
57 | */
58 | public static Set set() {
59 | return aggregators.keySet();
60 | }
61 |
62 | /**
63 | * Returns the aggregator corresponding to the given name.
64 | * @param name The name of the aggregator to get.
65 | * @throws NoSuchElementException if the given name doesn't exist.
66 | * @see #set
67 | */
68 | public static Aggregator get(final String name) {
69 | final Aggregator agg = aggregators.get(name);
70 | if (agg != null) {
71 | return agg;
72 | }
73 | throw new NoSuchElementException("No such aggregator: " + name);
74 | }
75 |
76 | private static final class Sum implements Aggregator {
77 |
78 | public long runLong(final Longs values) {
79 | long result = values.nextLongValue();
80 | while (values.hasNextValue()) {
81 | result += values.nextLongValue();
82 | }
83 | return result;
84 | }
85 |
86 | public double runDouble(final Doubles values) {
87 | double result = values.nextDoubleValue();
88 | while (values.hasNextValue()) {
89 | result += values.nextDoubleValue();
90 | }
91 | return result;
92 | }
93 |
94 | public String toString() {
95 | return "sum";
96 | }
97 |
98 | }
99 |
100 | private static final class Min implements Aggregator {
101 |
102 | public long runLong(final Longs values) {
103 | long min = values.nextLongValue();
104 | while (values.hasNextValue()) {
105 | final long val = values.nextLongValue();
106 | if (val < min) {
107 | min = val;
108 | }
109 | }
110 | return min;
111 | }
112 |
113 | public double runDouble(final Doubles values) {
114 | double min = values.nextDoubleValue();
115 | while (values.hasNextValue()) {
116 | final double val = values.nextDoubleValue();
117 | if (val < min) {
118 | min = val;
119 | }
120 | }
121 | return min;
122 | }
123 |
124 | public String toString() {
125 | return "min";
126 | }
127 |
128 | }
129 |
130 | private static final class Max implements Aggregator {
131 |
132 | public long runLong(final Longs values) {
133 | long max = values.nextLongValue();
134 | while (values.hasNextValue()) {
135 | final long val = values.nextLongValue();
136 | if (val > max) {
137 | max = val;
138 | }
139 | }
140 | return max;
141 | }
142 |
143 | public double runDouble(final Doubles values) {
144 | double max = values.nextDoubleValue();
145 | while (values.hasNextValue()) {
146 | final double val = values.nextDoubleValue();
147 | if (val > max) {
148 | max = val;
149 | }
150 | }
151 | return max;
152 | }
153 |
154 | public String toString() {
155 | return "max";
156 | }
157 |
158 | }
159 |
160 | private static final class Avg implements Aggregator {
161 |
162 | public long runLong(final Longs values) {
163 | long result = values.nextLongValue();
164 | int n = 1;
165 | while (values.hasNextValue()) {
166 | result += values.nextLongValue();
167 | n++;
168 | }
169 | return result / n;
170 | }
171 |
172 | public double runDouble(final Doubles values) {
173 | double result = values.nextDoubleValue();
174 | int n = 1;
175 | while (values.hasNextValue()) {
176 | result += values.nextDoubleValue();
177 | n++;
178 | }
179 | return result / n;
180 | }
181 |
182 | public String toString() {
183 | return "avg";
184 | }
185 | }
186 |
187 | /**
188 | * Standard Deviation aggregator.
189 | * Can compute without storing all of the data points in memory at the same
190 | * time. This implementation is based upon a
191 | * paper by John
192 | * D. Cook , which itself is based upon a method that goes back to a 1962
193 | * paper by B. P. Welford and is presented in Donald Knuth's Art of
194 | * Computer Programming, Vol 2, page 232, 3rd edition
195 | */
196 | private static final class StdDev implements Aggregator {
197 |
198 | public long runLong(final Longs values) {
199 | double old_mean = values.nextLongValue();
200 |
201 | if (!values.hasNextValue()) {
202 | return 0;
203 | }
204 |
205 | long n = 2;
206 | double new_mean = 0;
207 | double variance = 0;
208 | do {
209 | final double x = values.nextLongValue();
210 | new_mean = old_mean + (x - old_mean) / n;
211 | variance += (x - old_mean) * (x - new_mean);
212 | old_mean = new_mean;
213 | n++;
214 | } while (values.hasNextValue());
215 |
216 | return (long) Math.sqrt(variance / (n - 1));
217 | }
218 |
219 | public double runDouble(final Doubles values) {
220 | double old_mean = values.nextDoubleValue();
221 |
222 | if (!values.hasNextValue()) {
223 | return 0;
224 | }
225 |
226 | long n = 2;
227 | double new_mean = 0;
228 | double variance = 0;
229 | do {
230 | final double x = values.nextDoubleValue();
231 | new_mean = old_mean + (x - old_mean) / n;
232 | variance += (x - old_mean) * (x - new_mean);
233 | old_mean = new_mean;
234 | n++;
235 | } while (values.hasNextValue());
236 |
237 | return Math.sqrt(variance / (n - 1));
238 | }
239 |
240 | public String toString() {
241 | return "dev";
242 | }
243 | }
244 |
245 | }
246 |
--------------------------------------------------------------------------------
/test/tsd/TestGraphHandler.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2011-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.tsd;
14 |
15 | import java.io.File;
16 |
17 | import org.jboss.netty.channel.Channel;
18 |
19 | import org.junit.Test;
20 | import org.junit.runner.RunWith;
21 | import static org.junit.Assert.assertFalse;
22 | import static org.junit.Assert.assertTrue;
23 | import static org.mockito.Mockito.times;
24 | import static org.mockito.Mockito.verify;
25 | import static org.mockito.Mockito.when;
26 |
27 | import org.powermock.api.mockito.PowerMockito;
28 | import org.powermock.core.classloader.annotations.PowerMockIgnore;
29 | import org.powermock.core.classloader.annotations.PrepareForTest;
30 | import org.powermock.modules.junit4.PowerMockRunner;
31 | import org.powermock.reflect.Whitebox;
32 | import static org.powermock.api.mockito.PowerMockito.mock;
33 |
34 | @RunWith(PowerMockRunner.class)
35 | // "Classloader hell"... It's real. Tell PowerMock to ignore these classes
36 | // because they fiddle with the class loader. We don't test them anyway.
37 | @PowerMockIgnore({"javax.management.*", "javax.xml.*",
38 | "ch.qos.*", "org.slf4j.*",
39 | "com.sum.*", "org.xml.*"})
40 | @PrepareForTest({ GraphHandler.class, HttpQuery.class })
41 | public final class TestGraphHandler {
42 |
43 | @Test // If the file doesn't exist, we don't use it, obviously.
44 | public void staleCacheFileDoesntExist() throws Exception {
45 | final File cachedfile = fakeFile("/cache/fake-file");
46 | // From the JDK manual: "returns 0L if the file does not exist
47 | // or if an I/O error occurs"
48 | when(cachedfile.lastModified()).thenReturn(0L);
49 |
50 | assertTrue("File is stale", staleCacheFile(null, 0, 10, cachedfile));
51 |
52 | verify(cachedfile).lastModified(); // Ensure we do a single stat() call.
53 | }
54 |
55 | @Test // If the mtime of a file is in the future, we don't use it.
56 | public void staleCacheFileInTheFuture() throws Exception {
57 | PowerMockito.mockStatic(System.class);
58 |
59 | final HttpQuery query = fakeHttpQuery();
60 | final File cachedfile = fakeFile("/cache/fake-file");
61 |
62 | final long now = 1000L;
63 | when(System.currentTimeMillis()).thenReturn(now);
64 | when(cachedfile.lastModified()).thenReturn(now + 1000L);
65 | final long end_time = now;
66 |
67 | assertTrue("File is stale",
68 | staleCacheFile(query, end_time, 10, cachedfile));
69 |
70 | verify(cachedfile).lastModified(); // Ensure we do a single stat() call.
71 | PowerMockito.verifyStatic(); // Verify that ...
72 | System.currentTimeMillis(); // ... this was called only once.
73 | }
74 |
75 | @Test // End time in the future => OK to serve stale file up to max_age.
76 | public void staleCacheFileEndTimeInFuture() throws Exception {
77 | PowerMockito.mockStatic(System.class);
78 |
79 | final HttpQuery query = fakeHttpQuery();
80 | final File cachedfile = fakeFile("/cache/fake-file");
81 |
82 | final long end_time = 20000L;
83 | when(System.currentTimeMillis()).thenReturn(10000L);
84 | when(cachedfile.lastModified()).thenReturn(8000L);
85 |
86 | assertFalse("File is not more than 3s stale",
87 | staleCacheFile(query, end_time, 3, cachedfile));
88 | assertFalse("File is more than 2s stale",
89 | staleCacheFile(query, end_time, 2, cachedfile));
90 | assertTrue("File is more than 1s stale",
91 | staleCacheFile(query, end_time, 1, cachedfile));
92 |
93 | // Ensure that we stat() the file and look at the current time once per
94 | // invocation of staleCacheFile().
95 | verify(cachedfile, times(3)).lastModified();
96 | PowerMockito.verifyStatic(times(3));
97 | System.currentTimeMillis();
98 | }
99 |
100 | @Test // No end time = end time is now.
101 | public void staleCacheFileEndTimeIsNow() throws Exception {
102 | PowerMockito.mockStatic(System.class);
103 |
104 | final HttpQuery query = fakeHttpQuery();
105 | final File cachedfile = fakeFile("/cache/fake-file");
106 |
107 | final long now = 10000L;
108 | final long end_time = now;
109 | when(System.currentTimeMillis()).thenReturn(now);
110 | when(cachedfile.lastModified()).thenReturn(8000L);
111 |
112 | assertFalse("File is not more than 3s stale",
113 | staleCacheFile(query, end_time, 3, cachedfile));
114 | assertFalse("File is more than 2s stale",
115 | staleCacheFile(query, end_time, 2, cachedfile));
116 | assertTrue("File is more than 1s stale",
117 | staleCacheFile(query, end_time, 1, cachedfile));
118 |
119 | // Ensure that we stat() the file and look at the current time once per
120 | // invocation of staleCacheFile().
121 | verify(cachedfile, times(3)).lastModified();
122 | PowerMockito.verifyStatic(times(3));
123 | System.currentTimeMillis();
124 | }
125 |
126 | @Test // End time in the past, file's mtime predates it.
127 | public void staleCacheFileEndTimeInPastOlderFile() throws Exception {
128 | PowerMockito.mockStatic(System.class);
129 |
130 | final HttpQuery query = fakeHttpQuery();
131 | final File cachedfile = fakeFile("/cache/fake-file");
132 |
133 | final long end_time = 8000L;
134 | final long now = end_time + 2000L;
135 | when(System.currentTimeMillis()).thenReturn(now);
136 | when(cachedfile.lastModified()).thenReturn(5000L);
137 |
138 | assertTrue("File predates end-time and cannot be re-used",
139 | staleCacheFile(query, end_time, 4, cachedfile));
140 |
141 | verify(cachedfile).lastModified(); // Ensure we do a single stat() call.
142 | PowerMockito.verifyStatic(); // Verify that ...
143 | System.currentTimeMillis(); // ... this was called only once.
144 | }
145 |
146 | @Test // End time in the past, file's mtime is after it.
147 | public void staleCacheFileEndTimeInPastCacheableFile() throws Exception {
148 | PowerMockito.mockStatic(System.class);
149 |
150 | final HttpQuery query = fakeHttpQuery();
151 | final File cachedfile = fakeFile("/cache/fake-file");
152 |
153 | final long end_time = 8000L;
154 | final long now = end_time + 2000L;
155 | when(System.currentTimeMillis()).thenReturn(now);
156 | when(cachedfile.lastModified()).thenReturn(end_time + 1000L);
157 |
158 | assertFalse("File was created after end-time and can be re-used",
159 | staleCacheFile(query, end_time, 1, cachedfile));
160 |
161 | verify(cachedfile).lastModified(); // Ensure we do a single stat() call.
162 | PowerMockito.verifyStatic(); // Verify that ...
163 | System.currentTimeMillis(); // ... this was called only once.
164 | }
165 |
166 | /**
167 | * Helper to call private static method.
168 | * There's one slight difference: the {@code end_time} parameter is in
169 | * milliseconds here, instead of seconds.
170 | */
171 | private static boolean staleCacheFile(final HttpQuery query,
172 | final long end_time,
173 | final long max_age,
174 | final File cachedfile) throws Exception {
175 | return Whitebox.invokeMethod(GraphHandler.class, "staleCacheFile",
176 | query, end_time / 1000, max_age,
177 | cachedfile);
178 | }
179 |
180 | private static HttpQuery fakeHttpQuery() {
181 | final HttpQuery query = mock(HttpQuery.class);
182 | final Channel chan = fakeChannel();
183 | when(query.channel()).thenReturn(chan);
184 | return query;
185 | }
186 |
187 | private static Channel fakeChannel() {
188 | final Channel chan = mock(Channel.class);
189 | when(chan.toString()).thenReturn("[fake channel]");
190 | return chan;
191 | }
192 |
193 | private static File fakeFile(final String path) {
194 | final File file = mock(File.class);
195 | when(file.getPath()).thenReturn(path);
196 | when(file.toString()).thenReturn(path);
197 | return file;
198 | }
199 |
200 | }
201 |
--------------------------------------------------------------------------------
/src/stats/StatsCollector.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2010-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.stats;
14 |
15 | import org.slf4j.Logger;
16 | import org.slf4j.LoggerFactory;
17 |
18 | import java.net.InetAddress;
19 | import java.net.UnknownHostException;
20 | import java.util.HashMap;
21 | import java.util.Map;
22 |
23 | /**
24 | * Receives various stats/metrics from the current process.
25 | *
26 | * Instances of this class are passed around to other classes to collect
27 | * their stats/metrics and do something with them (presumably send them
28 | * to a client).
29 | *
30 | * This class does not do any synchronization and is not thread-safe.
31 | */
32 | public abstract class StatsCollector {
33 |
34 | private static final Logger LOG =
35 | LoggerFactory.getLogger(StatsCollector.class);
36 |
37 | /** Prefix to add to every metric name, for example `tsd'. */
38 | private final String prefix;
39 |
40 | /** Extra tags to add to every data point emitted. */
41 | private HashMap extratags;
42 |
43 | /** Buffer used to build lines emitted. */
44 | private final StringBuilder buf = new StringBuilder();
45 |
46 | /**
47 | * Constructor.
48 | * @param prefix A prefix to add to every metric name, for example
49 | * `tsd'.
50 | */
51 | public StatsCollector(final String prefix) {
52 | this.prefix = prefix;
53 | }
54 |
55 | /**
56 | * Method to override to actually emit a data point.
57 | * @param datapoint A data point in a format suitable for a text
58 | * import.
59 | */
60 | public abstract void emit(String datapoint);
61 |
62 | /**
63 | * Records a data point.
64 | * @param name The name of the metric.
65 | * @param value The current value for that metric.
66 | */
67 | public final void record(final String name, final long value) {
68 | record(name, value, null);
69 | }
70 |
71 | /**
72 | * Records a data point.
73 | * @param name The name of the metric.
74 | * @param value The current value for that metric.
75 | */
76 | public final void record(final String name, final Number value) {
77 | record(name, value.longValue(), null);
78 | }
79 |
80 | /**
81 | * Records a data point.
82 | * @param name The name of the metric.
83 | * @param value The current value for that metric.
84 | * @param xtratag An extra tag ({@code name=value}) to add to those
85 | * data points (ignored if {@code null}).
86 | * @throws IllegalArgumentException if {@code xtratag != null} and it
87 | * doesn't follow the {@code name=value} format.
88 | */
89 | public final void record(final String name,
90 | final Number value,
91 | final String xtratag) {
92 | record(name, value.longValue(), xtratag);
93 | }
94 |
95 | /**
96 | * Records a number of data points from a {@link Histogram}.
97 | * @param name The name of the metric.
98 | * @param histo The histogram to collect data points from.
99 | * @param xtratag An extra tag ({@code name=value}) to add to those
100 | * data points (ignored if {@code null}).
101 | * @throws IllegalArgumentException if {@code xtratag != null} and it
102 | * doesn't follow the {@code name=value} format.
103 | */
104 | public final void record(final String name,
105 | final Histogram histo,
106 | final String xtratag) {
107 | record(name + "_50pct", histo.percentile(50), xtratag);
108 | record(name + "_75pct", histo.percentile(75), xtratag);
109 | record(name + "_90pct", histo.percentile(90), xtratag);
110 | record(name + "_95pct", histo.percentile(95), xtratag);
111 | }
112 |
113 | /**
114 | * Records a data point.
115 | * @param name The name of the metric.
116 | * @param value The current value for that metric.
117 | * @param xtratag An extra tag ({@code name=value}) to add to this
118 | * data point (ignored if {@code null}).
119 | * @throws IllegalArgumentException if {@code xtratag != null} and it
120 | * doesn't follow the {@code name=value} format.
121 | */
122 | public final void record(final String name,
123 | final long value,
124 | final String xtratag) {
125 | buf.setLength(0);
126 | buf.append(prefix).append(".")
127 | .append(name)
128 | .append(' ')
129 | .append(System.currentTimeMillis() / 1000)
130 | .append(' ')
131 | .append(value);
132 |
133 | if (xtratag != null) {
134 | if (xtratag.indexOf('=') != xtratag.lastIndexOf('=')) {
135 | throw new IllegalArgumentException("invalid xtratag: " + xtratag
136 | + " (multiple '=' signs), name=" + name + ", value=" + value);
137 | } else if (xtratag.indexOf('=') < 0) {
138 | throw new IllegalArgumentException("invalid xtratag: " + xtratag
139 | + " (missing '=' signs), name=" + name + ", value=" + value);
140 | }
141 | buf.append(' ').append(xtratag);
142 | }
143 |
144 | if (extratags != null) {
145 | for (final Map.Entry entry : extratags.entrySet()) {
146 | buf.append(' ').append(entry.getKey())
147 | .append('=').append(entry.getValue());
148 | }
149 | }
150 | buf.append('\n');
151 | emit(buf.toString());
152 | }
153 |
154 | /**
155 | * Adds a tag to all the subsequent data points recorded.
156 | *
157 | * All subsequent calls to one of the {@code record} methods will
158 | * associate the tag given to this method with the data point.
159 | *
160 | * This method can be called multiple times to associate multiple tags
161 | * with all the subsequent data points.
162 | * @param name The name of the tag.
163 | * @param value The value of the tag.
164 | * @throws IllegalArgumentException if the name or the value are empty
165 | * or otherwise invalid.
166 | * @see #clearExtraTag
167 | */
168 | public final void addExtraTag(final String name, final String value) {
169 | if (name.length() <= 0) {
170 | throw new IllegalArgumentException("empty tag name, value=" + value);
171 | } else if (value.length() <= 0) {
172 | throw new IllegalArgumentException("empty value, tag name=" + name);
173 | } else if (name.indexOf('=') != -1) {
174 | throw new IllegalArgumentException("tag name contains `=': " + name
175 | + " (value = " + value + ')');
176 | } else if (value.indexOf('=') != -1) {
177 | throw new IllegalArgumentException("tag value contains `=': " + value
178 | + " (name = " + name + ')');
179 | }
180 | if (extratags == null) {
181 | extratags = new HashMap();
182 | }
183 | extratags.put(name, value);
184 | }
185 |
186 | /**
187 | * Adds a {@code host=hostname} tag.
188 | *
189 | * This uses {@link InetAddress#getLocalHost} to find the hostname of the
190 | * current host. If the hostname cannot be looked up, {@code (unknown)}
191 | * is used instead.
192 | */
193 | public final void addHostTag() {
194 | try {
195 | addExtraTag("host", InetAddress.getLocalHost().getHostName());
196 | } catch (UnknownHostException x) {
197 | LOG.error("WTF? Can't find hostname for localhost!", x);
198 | addExtraTag("host", "(unknown)");
199 | }
200 | }
201 |
202 | /**
203 | * Clears a tag added using {@link #addExtraTag addExtraTag}.
204 | * @param name The name of the tag to remove from the set of extra
205 | * tags.
206 | * @throws IllegalStateException if there's no extra tag currently
207 | * recorded.
208 | * @throws IllegalArgumentException if the given name isn't in the
209 | * set of extra tags currently recorded.
210 | * @see #addExtraTag
211 | */
212 | public final void clearExtraTag(final String name) {
213 | if (extratags == null) {
214 | throw new IllegalStateException("no extra tags added");
215 | }
216 | if (extratags.get(name) == null) {
217 | throw new IllegalArgumentException("tag '" + name
218 | + "' not in" + extratags);
219 | }
220 | extratags.remove(name);
221 | }
222 |
223 | }
224 |
--------------------------------------------------------------------------------
/src/tools/TSDMain.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2010-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.tools;
14 |
15 | import java.io.File;
16 | import java.net.InetAddress;
17 | import java.net.InetSocketAddress;
18 | import java.util.concurrent.Executors;
19 |
20 | import org.jboss.netty.channel.socket.ServerSocketChannelFactory;
21 | import org.jboss.netty.channel.socket.oio.OioServerSocketChannelFactory;
22 | import org.slf4j.Logger;
23 | import org.slf4j.LoggerFactory;
24 |
25 | import org.jboss.netty.bootstrap.ServerBootstrap;
26 | import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
27 |
28 | import org.hbase.async.HBaseClient;
29 |
30 | import net.opentsdb.BuildData;
31 | import net.opentsdb.core.TSDB;
32 | import net.opentsdb.tsd.PipelineFactory;
33 |
34 | /**
35 | * Main class of the TSD, the Time Series Daemon.
36 | */
37 | final class TSDMain {
38 |
39 | /** Prints usage and exits with the given retval. */
40 | static void usage(final ArgP argp, final String errmsg, final int retval) {
41 | System.err.println(errmsg);
42 | System.err.println("Usage: tsd --port=PORT"
43 | + " --staticroot=PATH --cachedir=PATH\n"
44 | + "Starts the TSD, the Time Series Daemon");
45 | if (argp != null) {
46 | System.err.print(argp.usage());
47 | }
48 | System.exit(retval);
49 | }
50 |
51 | private static final short DEFAULT_FLUSH_INTERVAL = 1000;
52 | private static final boolean DONT_CREATE = false;
53 | private static final boolean CREATE_IF_NEEDED = true;
54 | private static final boolean MUST_BE_WRITEABLE = true;
55 |
56 | /**
57 | * Ensures the given directory path is usable and set it as a system prop.
58 | * In case of problem, this function calls {@code System.exit}.
59 | * @param prop The name of the system property to set.
60 | * @param dir The path to the directory that needs to be checked.
61 | * @param need_write Whether or not the directory must be writeable.
62 | * @param create If {@code true}, the directory {@code dir} will be created
63 | * if it doesn't exist.
64 | */
65 | private static void setDirectoryInSystemProps(final String prop,
66 | final String dir,
67 | final boolean need_write,
68 | final boolean create) {
69 | final File f = new File(dir);
70 | final String path = f.getPath();
71 | if (!f.exists() && !(create && f.mkdirs())) {
72 | usage(null, "No such directory: " + path, 3);
73 | } else if (!f.isDirectory()) {
74 | usage(null, "Not a directory: " + path, 3);
75 | } else if (need_write && !f.canWrite()) {
76 | usage(null, "Cannot write to directory: " + path, 3);
77 | }
78 | System.setProperty(prop, path + '/');
79 | }
80 |
81 | public static void main(String[] args) {
82 | Logger log = LoggerFactory.getLogger(TSDMain.class);
83 | log.info("Starting.");
84 | log.info(BuildData.revisionString());
85 | log.info(BuildData.buildString());
86 | try {
87 | System.in.close(); // Release a FD we don't need.
88 | } catch (Exception e) {
89 | log.warn("Failed to close stdin", e);
90 | }
91 |
92 | final ArgP argp = new ArgP();
93 | CliOptions.addCommon(argp);
94 | argp.addOption("--port", "NUM", "TCP port to listen on.");
95 | argp.addOption("--bind", "ADDR", "Address to bind to (default: 0.0.0.0).");
96 | argp.addOption("--staticroot", "PATH",
97 | "Web root from which to serve static files (/s URLs).");
98 | argp.addOption("--cachedir", "PATH",
99 | "Directory under which to cache result of requests.");
100 | argp.addOption("--worker-threads", "NUM",
101 | "Number for async io workers (default: cpu * 2).");
102 | argp.addOption("--async-io", "true|false",
103 | "Use async NIO (default true) or traditional blocking io");
104 | argp.addOption("--flush-interval", "MSEC",
105 | "Maximum time for which a new data point can be buffered"
106 | + " (default: " + DEFAULT_FLUSH_INTERVAL + ").");
107 | CliOptions.addAutoMetricFlag(argp);
108 | args = CliOptions.parse(argp, args);
109 | if (args == null || !argp.has("--port")
110 | || !argp.has("--staticroot") || !argp.has("--cachedir")) {
111 | usage(argp, "Invalid usage.", 1);
112 | } else if (args.length != 0) {
113 | usage(argp, "Too many arguments.", 2);
114 | }
115 | args = null; // free().
116 |
117 | final short flush_interval = getFlushInterval(argp);
118 |
119 | setDirectoryInSystemProps("tsd.http.staticroot", argp.get("--staticroot"),
120 | DONT_CREATE, !MUST_BE_WRITEABLE);
121 | setDirectoryInSystemProps("tsd.http.cachedir", argp.get("--cachedir"),
122 | CREATE_IF_NEEDED, MUST_BE_WRITEABLE);
123 |
124 | final ServerSocketChannelFactory factory;
125 | if (argp.get("--async-io", "true").equalsIgnoreCase("true")) {
126 | final int workers;
127 | if (argp.has("--worker-threads")) {
128 | workers = Integer.parseInt(argp.get("--worker-threads"));
129 | } else {
130 | workers = Runtime.getRuntime().availableProcessors() * 2;
131 | }
132 | factory = new
133 | NioServerSocketChannelFactory(Executors.newCachedThreadPool(),
134 | Executors.newCachedThreadPool(),
135 | workers);
136 | } else {
137 | factory = new
138 | OioServerSocketChannelFactory(Executors.newCachedThreadPool(),
139 | Executors.newCachedThreadPool());
140 | }
141 | final HBaseClient client = CliOptions.clientFromOptions(argp);
142 | try {
143 | // Make sure we don't even start if we can't find out tables.
144 | final String table = argp.get("--table", "tsdb");
145 | final String uidtable = argp.get("--uidtable", "tsdb-uid");
146 | client.ensureTableExists(table).joinUninterruptibly();
147 | client.ensureTableExists(uidtable).joinUninterruptibly();
148 |
149 | client.setFlushInterval(flush_interval);
150 | final TSDB tsdb = new TSDB(client, table, uidtable);
151 | registerShutdownHook(tsdb);
152 | final ServerBootstrap server = new ServerBootstrap(factory);
153 |
154 | server.setPipelineFactory(new PipelineFactory(tsdb));
155 | server.setOption("child.tcpNoDelay", true);
156 | server.setOption("child.keepAlive", true);
157 | server.setOption("reuseAddress", true);
158 |
159 | // null is interpreted as the wildcard address.
160 | InetAddress bindAddress = null;
161 | if (argp.has("--bind")) {
162 | bindAddress = InetAddress.getByName(argp.get("--bind"));
163 | }
164 |
165 | final InetSocketAddress addr =
166 | new InetSocketAddress(bindAddress, Integer.parseInt(argp.get("--port")));
167 | server.bind(addr);
168 | log.info("Ready to serve on " + addr);
169 | } catch (Throwable e) {
170 | factory.releaseExternalResources();
171 | try {
172 | client.shutdown().joinUninterruptibly();
173 | } catch (Exception e2) {
174 | log.error("Failed to shutdown HBase client", e2);
175 | }
176 | throw new RuntimeException("Initialization failed", e);
177 | }
178 | // The server is now running in separate threads, we can exit main.
179 | }
180 |
181 | /**
182 | * Parses the value of the --flush-interval parameter.
183 | * @throws IllegalArgumentException if the flush interval is negative.
184 | * @return The flush interval.
185 | */
186 | private static short getFlushInterval(final ArgP argp) {
187 | final String flush_arg = argp.get("--flush-interval");
188 | if (flush_arg == null) {
189 | return DEFAULT_FLUSH_INTERVAL;
190 | }
191 | final short flush_interval = Short.parseShort(flush_arg);
192 | if (flush_interval < 0) {
193 | throw new IllegalArgumentException("Negative --flush-interval: "
194 | + flush_interval);
195 | }
196 | return flush_interval;
197 | }
198 |
199 | private static void registerShutdownHook(final TSDB tsdb) {
200 | final class TSDBShutdown extends Thread {
201 | public TSDBShutdown() {
202 | super("TSDBShutdown");
203 | }
204 | public void run() {
205 | try {
206 | tsdb.shutdown().join();
207 | } catch (Exception e) {
208 | LoggerFactory.getLogger(TSDBShutdown.class)
209 | .error("Uncaught exception during shutdown", e);
210 | }
211 | }
212 | }
213 | Runtime.getRuntime().addShutdownHook(new TSDBShutdown());
214 | }
215 |
216 | }
217 |
--------------------------------------------------------------------------------
/src/tools/TextImporter.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2010-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.tools;
14 |
15 | import java.io.BufferedReader;
16 | import java.io.FileInputStream;
17 | import java.io.IOException;
18 | import java.io.InputStream;
19 | import java.io.InputStreamReader;
20 | import java.util.HashMap;
21 | import java.util.zip.GZIPInputStream;
22 |
23 | import com.stumbleupon.async.Callback;
24 | import com.stumbleupon.async.Deferred;
25 |
26 | import org.slf4j.Logger;
27 | import org.slf4j.LoggerFactory;
28 |
29 | import org.hbase.async.HBaseClient;
30 | import org.hbase.async.HBaseRpc;
31 | import org.hbase.async.PleaseThrottleException;
32 | import org.hbase.async.PutRequest;
33 |
34 | import net.opentsdb.core.Tags;
35 | import net.opentsdb.core.TSDB;
36 | import net.opentsdb.core.WritableDataPoints;
37 | import net.opentsdb.stats.StatsCollector;
38 |
39 | final class TextImporter {
40 |
41 | private static final Logger LOG = LoggerFactory.getLogger(TextImporter.class);
42 |
43 | /** Prints usage and exits with the given retval. */
44 | static void usage(final ArgP argp, final int retval) {
45 | System.err.println("Usage: import path [more paths]");
46 | System.err.print(argp.usage());
47 | System.err.println("This tool can directly read gzip'ed input files.");
48 | System.exit(retval);
49 | }
50 |
51 | public static void main(String[] args) throws IOException {
52 | ArgP argp = new ArgP();
53 | CliOptions.addCommon(argp);
54 | CliOptions.addAutoMetricFlag(argp);
55 | args = CliOptions.parse(argp, args);
56 | if (args == null) {
57 | usage(argp, 1);
58 | } else if (args.length < 1) {
59 | usage(argp, 2);
60 | }
61 |
62 | final HBaseClient client = CliOptions.clientFromOptions(argp);
63 | // Flush more frequently since we read very fast from the files.
64 | client.setFlushInterval((short) 500); // ms
65 | final TSDB tsdb = new TSDB(client, argp.get("--table", "tsdb"),
66 | argp.get("--uidtable", "tsdb-uid"));
67 | argp = null;
68 | try {
69 | int points = 0;
70 | final long start_time = System.nanoTime();
71 | for (final String path : args) {
72 | points += importFile(client, tsdb, path);
73 | }
74 | final double time_delta = (System.nanoTime() - start_time) / 1000000000.0;
75 | LOG.info(String.format("Total: imported %d data points in %.3fs"
76 | + " (%.1f points/s)",
77 | points, time_delta, (points / time_delta)));
78 | // TODO(tsuna): Figure out something better than just writing to stderr.
79 | tsdb.collectStats(new StatsCollector("tsd") {
80 | @Override
81 | public final void emit(final String line) {
82 | System.err.print(line);
83 | }
84 | });
85 | } finally {
86 | try {
87 | tsdb.shutdown().joinUninterruptibly();
88 | } catch (Exception e) {
89 | LOG.error("Unexpected exception", e);
90 | System.exit(1);
91 | }
92 | }
93 | }
94 |
95 | static volatile boolean throttle = false;
96 |
97 | private static int importFile(final HBaseClient client,
98 | final TSDB tsdb,
99 | final String path) throws IOException {
100 | final long start_time = System.nanoTime();
101 | long ping_start_time = start_time;
102 | final BufferedReader in = open(path);
103 | String line = null;
104 | int points = 0;
105 | try {
106 | final class Errback implements Callback {
107 | public Object call(final Exception arg) {
108 | if (arg instanceof PleaseThrottleException) {
109 | final PleaseThrottleException e = (PleaseThrottleException) arg;
110 | LOG.warn("Need to throttle, HBase isn't keeping up.", e);
111 | throttle = true;
112 | final HBaseRpc rpc = e.getFailedRpc();
113 | if (rpc instanceof PutRequest) {
114 | client.put((PutRequest) rpc); // Don't lose edits.
115 | }
116 | return null;
117 | }
118 | LOG.error("Exception caught while processing file "
119 | + path, arg);
120 | System.exit(2);
121 | return arg;
122 | }
123 | public String toString() {
124 | return "importFile errback";
125 | }
126 | };
127 | final Errback errback = new Errback();
128 | while ((line = in.readLine()) != null) {
129 | final String[] words = Tags.splitString(line, ' ');
130 | final String metric = words[0];
131 | if (metric.length() <= 0) {
132 | throw new RuntimeException("invalid metric: " + metric);
133 | }
134 | final long timestamp = Tags.parseLong(words[1]);
135 | if (timestamp <= 0) {
136 | throw new RuntimeException("invalid timestamp: " + timestamp);
137 | }
138 | final String value = words[2];
139 | if (value.length() <= 0) {
140 | throw new RuntimeException("invalid value: " + value);
141 | }
142 | final HashMap tags = new HashMap();
143 | for (int i = 3; i < words.length; i++) {
144 | if (!words[i].isEmpty()) {
145 | Tags.parse(tags, words[i]);
146 | }
147 | }
148 | final WritableDataPoints dp = getDataPoints(tsdb, metric, tags);
149 | Deferred d;
150 | if (Tags.looksLikeInteger(value)) {
151 | d = dp.addPoint(timestamp, Tags.parseLong(value));
152 | } else { // floating point value
153 | d = dp.addPoint(timestamp, Float.parseFloat(value));
154 | }
155 | d.addErrback(errback);
156 | points++;
157 | if (points % 1000000 == 0) {
158 | final long now = System.nanoTime();
159 | ping_start_time = (now - ping_start_time) / 1000000;
160 | LOG.info(String.format("... %d data points in %dms (%.1f points/s)",
161 | points, ping_start_time,
162 | (1000000 * 1000.0 / ping_start_time)));
163 | ping_start_time = now;
164 | }
165 | if (throttle) {
166 | LOG.info("Throttling...");
167 | long throttle_time = System.nanoTime();
168 | try {
169 | d.joinUninterruptibly();
170 | } catch (Exception e) {
171 | throw new RuntimeException("Should never happen", e);
172 | }
173 | throttle_time = System.nanoTime() - throttle_time;
174 | if (throttle_time < 1000000000L) {
175 | LOG.info("Got throttled for only " + throttle_time + "ns, sleeping a bit now");
176 | try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException("interrupted", e); }
177 | }
178 | LOG.info("Done throttling...");
179 | throttle = false;
180 | }
181 | }
182 | } catch (RuntimeException e) {
183 | LOG.error("Exception caught while processing file "
184 | + path + " line=" + line);
185 | throw e;
186 | } finally {
187 | in.close();
188 | }
189 | final long time_delta = (System.nanoTime() - start_time) / 1000000;
190 | LOG.info(String.format("Processed %s in %d ms, %d data points"
191 | + " (%.1f points/s)",
192 | path, time_delta, points,
193 | (points * 1000.0 / time_delta)));
194 | return points;
195 | }
196 |
197 | /**
198 | * Opens a file for reading, handling gzipped files.
199 | * @param path The file to open.
200 | * @return A buffered reader to read the file, decompressing it if needed.
201 | * @throws IOException when shit happens.
202 | */
203 | private static BufferedReader open(final String path) throws IOException {
204 | InputStream is = new FileInputStream(path);
205 | if (path.endsWith(".gz")) {
206 | is = new GZIPInputStream(is);
207 | }
208 | // I <3 Java's IO library.
209 | return new BufferedReader(new InputStreamReader(is));
210 | }
211 |
212 | private static final HashMap datapoints =
213 | new HashMap();
214 |
215 | private static
216 | WritableDataPoints getDataPoints(final TSDB tsdb,
217 | final String metric,
218 | final HashMap tags) {
219 | final String key = metric + tags;
220 | WritableDataPoints dp = datapoints.get(key);
221 | if (dp != null) {
222 | return dp;
223 | }
224 | dp = tsdb.newDataPoints();
225 | dp.setSeries(metric, tags);
226 | dp.setBatchImport(true);
227 | datapoints.put(key, dp);
228 | return dp;
229 | }
230 |
231 | }
232 |
--------------------------------------------------------------------------------
/src/tools/DumpSeries.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2010-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.tools;
14 |
15 | import java.util.ArrayList;
16 | import java.util.Arrays;
17 | import java.util.Date;
18 | import java.util.Map;
19 |
20 | import org.hbase.async.Bytes;
21 | import org.hbase.async.DeleteRequest;
22 | import org.hbase.async.HBaseClient;
23 | import org.hbase.async.KeyValue;
24 | import org.hbase.async.Scanner;
25 |
26 | import net.opentsdb.core.IllegalDataException;
27 | import net.opentsdb.core.Internal;
28 | import net.opentsdb.core.Query;
29 | import net.opentsdb.core.TSDB;
30 |
31 | /**
32 | * Tool to dump the data straight from HBase.
33 | * Useful for debugging data induced problems.
34 | */
35 | final class DumpSeries {
36 |
37 | /** Prints usage and exits with the given retval. */
38 | private static void usage(final ArgP argp, final String errmsg,
39 | final int retval) {
40 | System.err.println(errmsg);
41 | System.err.println("Usage: scan"
42 | + " [--delete|--import] START-DATE [END-DATE] query [queries...]\n"
43 | + "To see the format in which queries should be written, see the help"
44 | + " of the 'query' command.\n"
45 | + "The --import flag changes the format in which the output is printed"
46 | + " to use a format suiteable for the 'import' command instead of the"
47 | + " default output format, which better represents how the data is"
48 | + " stored in HBase.\n"
49 | + "The --delete flag will delete every row matched by the query."
50 | + " This flag implies --import.");
51 | System.err.print(argp.usage());
52 | System.exit(retval);
53 | }
54 |
55 | public static void main(String[] args) throws Exception {
56 | ArgP argp = new ArgP();
57 | CliOptions.addCommon(argp);
58 | argp.addOption("--import", "Prints the rows in a format suitable for"
59 | + " the 'import' command.");
60 | argp.addOption("--delete", "Deletes rows as they are scanned.");
61 | args = CliOptions.parse(argp, args);
62 | if (args == null) {
63 | usage(argp, "Invalid usage.", 1);
64 | } else if (args.length < 3) {
65 | usage(argp, "Not enough arguments.", 2);
66 | }
67 |
68 | final HBaseClient client = CliOptions.clientFromOptions(argp);
69 | final byte[] table = argp.get("--table", "tsdb").getBytes();
70 | final TSDB tsdb = new TSDB(client, argp.get("--table", "tsdb"),
71 | argp.get("--uidtable", "tsdb-uid"));
72 | final boolean delete = argp.has("--delete");
73 | final boolean importformat = delete || argp.has("--import");
74 | argp = null;
75 | try {
76 | doDump(tsdb, client, table, delete, importformat, args);
77 | } finally {
78 | tsdb.shutdown().joinUninterruptibly();
79 | }
80 | }
81 |
82 | private static void doDump(final TSDB tsdb,
83 | final HBaseClient client,
84 | final byte[] table,
85 | final boolean delete,
86 | final boolean importformat,
87 | final String[] args) throws Exception {
88 | final ArrayList queries = new ArrayList();
89 | CliQuery.parseCommandLineQuery(args, tsdb, queries, null, null);
90 |
91 | final StringBuilder buf = new StringBuilder();
92 | for (final Query query : queries) {
93 | final Scanner scanner = Internal.getScanner(query);
94 | ArrayList> rows;
95 | while ((rows = scanner.nextRows().joinUninterruptibly()) != null) {
96 | for (final ArrayList row : rows) {
97 | buf.setLength(0);
98 | final byte[] key = row.get(0).key();
99 | final long base_time = Internal.baseTime(tsdb, key);
100 | final String metric = Internal.metricName(tsdb, key);
101 | // Print the row key.
102 | if (!importformat) {
103 | buf.append(Arrays.toString(key))
104 | .append(' ')
105 | .append(metric)
106 | .append(' ')
107 | .append(base_time)
108 | .append(" (").append(date(base_time)).append(") ");
109 | try {
110 | buf.append(Internal.getTags(tsdb, key));
111 | } catch (RuntimeException e) {
112 | buf.append(e.getClass().getName() + ": " + e.getMessage());
113 | }
114 | buf.append('\n');
115 | System.out.print(buf);
116 | }
117 |
118 | // Print individual cells.
119 | buf.setLength(0);
120 | if (!importformat) {
121 | buf.append(" ");
122 | }
123 | for (final KeyValue kv : row) {
124 | // Discard everything or keep initial spaces.
125 | buf.setLength(importformat ? 0 : 2);
126 | formatKeyValue(buf, tsdb, importformat, kv, base_time, metric);
127 | buf.append('\n');
128 | System.out.print(buf);
129 | }
130 |
131 | if (delete) {
132 | final DeleteRequest del = new DeleteRequest(table, key);
133 | client.delete(del);
134 | }
135 | }
136 | }
137 | }
138 | }
139 |
140 | static void formatKeyValue(final StringBuilder buf,
141 | final TSDB tsdb,
142 | final KeyValue kv,
143 | final long base_time) {
144 | formatKeyValue(buf, tsdb, true, kv, base_time,
145 | Internal.metricName(tsdb, kv.key()));
146 | }
147 |
148 | private static void formatKeyValue(final StringBuilder buf,
149 | final TSDB tsdb,
150 | final boolean importformat,
151 | final KeyValue kv,
152 | final long base_time,
153 | final String metric) {
154 | if (importformat) {
155 | buf.append(metric).append(' ');
156 | }
157 | final byte[] qualifier = kv.qualifier();
158 | final byte[] cell = kv.value();
159 | if (qualifier.length != 2 && cell[cell.length - 1] != 0) {
160 | throw new IllegalDataException("Don't know how to read this value:"
161 | + Arrays.toString(cell) + " found in " + kv
162 | + " -- this compacted value might have been written by a future"
163 | + " version of OpenTSDB, or could be corrupt.");
164 | }
165 | final int nvalues = qualifier.length / 2;
166 | final boolean multi_val = nvalues != 1 && !importformat;
167 | if (multi_val) {
168 | buf.append(Arrays.toString(qualifier))
169 | .append(' ').append(Arrays.toString(cell))
170 | .append(" = ").append(nvalues).append(" values:");
171 | }
172 |
173 | final String tags;
174 | if (importformat) {
175 | final StringBuilder tagsbuf = new StringBuilder();
176 | for (final Map.Entry tag
177 | : Internal.getTags(tsdb, kv.key()).entrySet()) {
178 | tagsbuf.append(' ').append(tag.getKey())
179 | .append('=').append(tag.getValue());
180 | }
181 | tags = tagsbuf.toString();
182 | } else {
183 | tags = null;
184 | }
185 |
186 | int value_offset = 0;
187 | for (int i = 0; i < nvalues; i++) {
188 | if (multi_val) {
189 | buf.append("\n ");
190 | }
191 | final short qual = Bytes.getShort(qualifier, i * 2);
192 | final byte flags = (byte) qual;
193 | final int value_len = (flags & 0x7) + 1;
194 | final short delta = (short) ((0x0000FFFF & qual) >>> 4);
195 | if (importformat) {
196 | buf.append(base_time + delta).append(' ');
197 | } else {
198 | final byte[] v = multi_val
199 | ? Arrays.copyOfRange(cell, value_offset, value_offset + value_len)
200 | : cell;
201 | buf.append(Arrays.toString(Bytes.fromShort(qual)))
202 | .append(' ')
203 | .append(Arrays.toString(v))
204 | .append('\t')
205 | .append(delta)
206 | .append('\t');
207 | }
208 | if ((qual & 0x8) == 0x8) {
209 | if (cell.length == 8 && value_len == 4
210 | && cell[0] == 0 && cell[1] == 0 && cell[2] == 0 && cell[3] == 0) {
211 | // Incorrect encoded floating point value.
212 | // See CompactionQueue.fixFloatingPointValue() for more details.
213 | value_offset += 4;
214 | }
215 | buf.append(importformat ? "" : "f ")
216 | .append(Internal.extractFloatingPointValue(cell, value_offset, flags));
217 | } else {
218 | buf.append(importformat ? "" : "l ")
219 | .append(Internal.extractIntegerValue(cell, value_offset, flags));
220 | }
221 | if (importformat) {
222 | buf.append(tags);
223 | if (nvalues > 1 && i + 1 < nvalues) {
224 | buf.append('\n').append(metric).append(' ');
225 | }
226 | } else {
227 | buf.append('\t')
228 | .append(base_time + delta)
229 | .append(" (").append(date(base_time + delta)).append(')');
230 | }
231 | value_offset += value_len;
232 | }
233 | }
234 |
235 | /** Transforms a UNIX timestamp into a human readable date. */
236 | static String date(final long timestamp) {
237 | return new Date(timestamp * 1000).toString();
238 | }
239 |
240 | }
241 |
--------------------------------------------------------------------------------
/src/tools/CliQuery.java:
--------------------------------------------------------------------------------
1 | // This file is part of OpenTSDB.
2 | // Copyright (C) 2010-2012 The OpenTSDB Authors.
3 | //
4 | // This program is free software: you can redistribute it and/or modify it
5 | // under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 2.1 of the License, or (at your
7 | // option) any later version. This program is distributed in the hope that it
8 | // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 | // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
10 | // General Public License for more details. You should have received a copy
11 | // of the GNU Lesser General Public License along with this program. If not,
12 | // see .
13 | package net.opentsdb.tools;
14 |
15 | import java.io.IOException;
16 | import java.text.ParseException;
17 | import java.text.SimpleDateFormat;
18 | import java.util.ArrayList;
19 | import java.util.HashMap;
20 |
21 | import org.slf4j.Logger;
22 | import org.slf4j.LoggerFactory;
23 |
24 | import org.hbase.async.HBaseClient;
25 |
26 | import net.opentsdb.core.Aggregator;
27 | import net.opentsdb.core.Aggregators;
28 | import net.opentsdb.core.Query;
29 | import net.opentsdb.core.DataPoint;
30 | import net.opentsdb.core.DataPoints;
31 | import net.opentsdb.core.Tags;
32 | import net.opentsdb.core.TSDB;
33 | import net.opentsdb.graph.Plot;
34 |
35 | final class CliQuery {
36 |
37 | private static final Logger LOG = LoggerFactory.getLogger(CliQuery.class);
38 |
39 | /** Prints usage and exits with the given retval. */
40 | private static void usage(final ArgP argp, final String errmsg,
41 | final int retval) {
42 | System.err.println(errmsg);
43 | System.err.println("Usage: query"
44 | + " [Gnuplot opts] START-DATE [END-DATE] [queries...]\n"
45 | + "A query has the form:\n"
46 | + " FUNC [rate] [downsample FUNC N] SERIES [TAGS]\n"
47 | + "For example:\n"
48 | + " 2010/03/11-20:57 sum my.awsum.metric host=blah"
49 | + " sum some.other.metric host=blah state=foo\n"
50 | + "Dates must follow this format: [YYYY/MM/DD-]HH:MM[:SS]\n"
51 | + "Supported values for FUNC: " + Aggregators.set()
52 | + "\nGnuplot options are of the form: +option=value");
53 | if (argp != null) {
54 | System.err.print(argp.usage());
55 | }
56 | System.exit(retval);
57 | }
58 |
59 | /** Parses the date in argument and returns a UNIX timestamp in seconds. */
60 | private static long parseDate(final String s) {
61 | SimpleDateFormat format;
62 | switch (s.length()) {
63 | case 5:
64 | format = new SimpleDateFormat("HH:mm");
65 | break;
66 | case 8:
67 | format = new SimpleDateFormat("HH:mm:ss");
68 | break;
69 | case 10:
70 | format = new SimpleDateFormat("yyyy/MM/dd");
71 | break;
72 | case 16:
73 | format = new SimpleDateFormat("yyyy/MM/dd-HH:mm");
74 | break;
75 | case 19:
76 | format = new SimpleDateFormat("yyyy/MM/dd-HH:mm:ss");
77 | break;
78 | default:
79 | usage(null, "Invalid date: " + s, 3);
80 | return -1; // Never executed as usage() exits.
81 | }
82 | try {
83 | return format.parse(s).getTime() / 1000;
84 | } catch (ParseException e) {
85 | usage(null, "Invalid date: " + s, 3);
86 | return -1; // Never executed as usage() exits.
87 | }
88 | }
89 |
90 | public static void main(String[] args) throws IOException {
91 | ArgP argp = new ArgP();
92 | CliOptions.addCommon(argp);
93 | CliOptions.addVerbose(argp);
94 | argp.addOption("--graph", "BASEPATH",
95 | "Output data points to a set of files for gnuplot."
96 | + " The path of the output files will start with"
97 | + " BASEPATH.");
98 | args = CliOptions.parse(argp, args);
99 | if (args == null) {
100 | usage(argp, "Invalid usage.", 1);
101 | } else if (args.length < 3) {
102 | usage(argp, "Not enough arguments.", 2);
103 | }
104 |
105 | final HBaseClient client = CliOptions.clientFromOptions(argp);
106 | final TSDB tsdb = new TSDB(client, argp.get("--table", "tsdb"),
107 | argp.get("--uidtable", "tsdb-uid"));
108 | final String basepath = argp.get("--graph");
109 | argp = null;
110 |
111 | Plot plot = null;
112 | try {
113 | plot = doQuery(tsdb, args, basepath != null);
114 | } finally {
115 | try {
116 | tsdb.shutdown().joinUninterruptibly();
117 | } catch (Exception e) {
118 | LOG.error("Unexpected exception", e);
119 | System.exit(1);
120 | }
121 | }
122 |
123 | if (plot != null) {
124 | try {
125 | final int npoints = plot.dumpToFiles(basepath);
126 | LOG.info("Wrote " + npoints + " for Gnuplot");
127 | } catch (IOException e) {
128 | LOG.error("Failed to write the Gnuplot file under " + basepath, e);
129 | System.exit(1);
130 | }
131 | }
132 | }
133 |
134 | private static Plot doQuery(final TSDB tsdb,
135 | final String args[],
136 | final boolean want_plot) {
137 | final ArrayList plotparams = new ArrayList();
138 | final ArrayList queries = new ArrayList();
139 | final ArrayList plotoptions = new ArrayList();
140 | parseCommandLineQuery(args, tsdb, queries, plotparams, plotoptions);
141 | if (queries.isEmpty()) {
142 | usage(null, "Not enough arguments, need at least one query.", 2);
143 | }
144 |
145 | final Plot plot = (want_plot ? new Plot(queries.get(0).getStartTime(),
146 | queries.get(0).getEndTime())
147 | : null);
148 | if (want_plot) {
149 | plot.setParams(parsePlotParams(plotparams));
150 | }
151 | final int nqueries = queries.size();
152 | for (int i = 0; i < nqueries; i++) {
153 | // TODO(tsuna): Optimization: run each query in parallel.
154 | final StringBuilder buf = want_plot ? null : new StringBuilder();
155 | for (final DataPoints datapoints : queries.get(i).run()) {
156 | if (want_plot) {
157 | plot.add(datapoints, plotoptions.get(i));
158 | } else {
159 | final String metric = datapoints.metricName();
160 | final String tagz = datapoints.getTags().toString();
161 | for (final DataPoint datapoint : datapoints) {
162 | buf.append(metric)
163 | .append(' ')
164 | .append(datapoint.timestamp())
165 | .append(' ');
166 | if (datapoint.isInteger()) {
167 | buf.append(datapoint.longValue());
168 | } else {
169 | buf.append(String.format("%f", datapoint.doubleValue()));
170 | }
171 | buf.append(' ').append(tagz).append('\n');
172 | System.out.print(buf);
173 | buf.delete(0, buf.length());
174 | }
175 | }
176 | }
177 | }
178 | return plot;
179 | }
180 |
181 | /**
182 | * Parses the query from the command lines.
183 | * @param args The command line arguments.
184 | * @param tsdb The TSDB to use.
185 | * @param queries The list in which {@link Query}s will be appended.
186 | * @param plotparams The list in which global plot parameters will be
187 | * appended. Ignored if {@code null}.
188 | * @param plotoptions The list in which per-line plot options will be
189 | * appended. Ignored if {@code null}.
190 | */
191 | static void parseCommandLineQuery(final String[] args,
192 | final TSDB tsdb,
193 | final ArrayList queries,
194 | final ArrayList plotparams,
195 | final ArrayList plotoptions) {
196 | final long start_ts = parseDate(args[0]);
197 | final long end_ts = (args.length > 3
198 | && (args[1].charAt(0) != '+'
199 | && (args[1].indexOf(':') >= 0
200 | || args[1].indexOf('/') >= 0))
201 | ? parseDate(args[1]) : -1);
202 |
203 | int i = end_ts < 0 ? 1 : 2;
204 | while (i < args.length && args[i].charAt(0) == '+') {
205 | if (plotparams != null) {
206 | plotparams.add(args[i]);
207 | }
208 | i++;
209 | }
210 |
211 | while (i < args.length) {
212 | final Aggregator agg = Aggregators.get(args[i++]);
213 | final boolean rate = args[i].equals("rate");
214 | if (rate) {
215 | i++;
216 | }
217 | final boolean downsample = args[i].equals("downsample");
218 | if (downsample) {
219 | i++;
220 | }
221 | final int interval = downsample ? Integer.parseInt(args[i++]) : 0;
222 | final Aggregator sampler = downsample ? Aggregators.get(args[i++]) : null;
223 | final String metric = args[i++];
224 | final HashMap tags = new HashMap();
225 | while (i < args.length && args[i].indexOf(' ', 1) < 0
226 | && args[i].indexOf('=', 1) > 0) {
227 | Tags.parse(tags, args[i++]);
228 | }
229 | if (i < args.length && args[i].indexOf(' ', 1) > 0) {
230 | plotoptions.add(args[i++]);
231 | }
232 | final Query query = tsdb.newQuery();
233 | query.setStartTime(start_ts);
234 | if (end_ts > 0) {
235 | query.setEndTime(end_ts);
236 | }
237 | query.setTimeSeries(metric, tags, agg, rate);
238 | if (downsample) {
239 | query.downsample(interval, sampler);
240 | }
241 | queries.add(query);
242 | }
243 | }
244 |
245 | private static HashMap parsePlotParams(final ArrayList params) {
246 | final HashMap result =
247 | new HashMap(params.size());
248 | for (final String param : params) {
249 | Tags.parse(result, param.substring(1));
250 | }
251 | return result;
252 | }
253 |
254 | }
255 |
--------------------------------------------------------------------------------