From 506f6ea4fc3d407ada577551d15f216f6453f840 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Fri, 11 Sep 2015 18:12:54 -0700 Subject: [PATCH 001/233] Bump to version 2.3.0-SNAPSHOT Signed-off-by: Chris Larsen --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 29d2615325..0b8dcdd76e 100644 --- a/configure.ac +++ b/configure.ac @@ -14,7 +14,7 @@ # along with this library. If not, see . # Semantic Versioning (see http://semver.org/). -AC_INIT([opentsdb], [2.2.0-SNAPSHOT], [opentsdb@googlegroups.com]) +AC_INIT([opentsdb], [2.3.0-SNAPSHOT], [opentsdb@googlegroups.com]) AC_CONFIG_AUX_DIR([build-aux]) AM_INIT_AUTOMAKE([foreign]) From 766b32bdd58af0858959813cfab917519c5715b9 Mon Sep 17 00:00:00 2001 From: Kieren Hynd Date: Tue, 6 Oct 2015 22:39:55 +0100 Subject: [PATCH 002/233] Allow "tsdb import" to read from STDIN --- src/tools/TextImporter.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tools/TextImporter.java b/src/tools/TextImporter.java index a8c72ef586..2f33f009be 100644 --- a/src/tools/TextImporter.java +++ b/src/tools/TextImporter.java @@ -259,6 +259,10 @@ public String toString() { * @throws IOException when shit happens. */ private static BufferedReader open(final String path) throws IOException { + if (path.equals("-")) { + return new BufferedReader(new InputStreamReader(System.in)); + } + InputStream is = new FileInputStream(path); if (path.endsWith(".gz")) { is = new GZIPInputStream(is); From 6dfe801822c320ae02683beda9a282c38dbd46f1 Mon Sep 17 00:00:00 2001 From: Hutt Li Date: Thu, 8 Oct 2015 11:57:53 +1030 Subject: [PATCH 003/233] enable /api/uid/rename endpoint --- src/core/TSDB.java | 46 ++++++++ src/tsd/HttpJsonSerializer.java | 29 +++++ src/tsd/HttpSerializer.java | 25 +++++ src/tsd/UniqueIdRpc.java | 67 ++++++++++++ src/uid/UniqueId.java | 23 ++++ test/core/TestTSDB.java | 46 ++++++++ test/tsd/TestHttpJsonSerializer.java | 63 +++++++++++ test/tsd/TestUniqueIdRpc.java | 152 +++++++++++++++++++++++++++ test/uid/TestUniqueId.java | 89 ++++++++++++++++ 9 files changed, 540 insertions(+) diff --git a/src/core/TSDB.java b/src/core/TSDB.java index 64cf75c779..c6a8e31410 100644 --- a/src/core/TSDB.java +++ b/src/core/TSDB.java @@ -1115,6 +1115,52 @@ public Deferred deleteUidAsync(final String type, final String name) { } } + /** + * Attempts to rename a UID from existing name to the given name + * Used by the UniqueIdRpc call to rename name of existing metrics, tagks or + * tagvs. The name must pass validation. If the UID doesn't exist, the method + * will throw an error. Chained IllegalArgumentException is directly exposed + * to caller. If the rename was successful, this method returns. + * @param type The type of uid to rename, one of metric, tagk and tagv + * @param oldname The existing name of the uid object + * @param newname The new name to be used on the uid object + * @throws IllegalArgumentException if error happened + * @since 2.2 + */ + public void renameUid(final String type, final String oldname, + final String newname) { + Tags.validateString(type, oldname); + Tags.validateString(type, newname); + if (type.toLowerCase().equals("metric")) { + try { + this.metrics.getId(oldname); + this.metrics.rename(oldname, newname); + } catch (NoSuchUniqueName nsue) { + throw new IllegalArgumentException("Name(\"" + oldname + + "\") does not exist"); + } + } else if (type.toLowerCase().equals("tagk")) { + try { + this.tag_names.getId(oldname); + this.tag_names.rename(oldname, newname); + } catch (NoSuchUniqueName nsue) { + throw new IllegalArgumentException("Name(\"" + oldname + + "\") does not exist"); + } + } else if (type.toLowerCase().equals("tagv")) { + try { + this.tag_values.getId(oldname); + this.tag_values.rename(oldname, newname); + } catch (NoSuchUniqueName nsue) { + throw new IllegalArgumentException("Name(\"" + oldname + + "\") does not exist"); + } + } else { + LOG.warn("Unknown type name: " + type); + throw new IllegalArgumentException("Unknown type name"); + } + } + /** @return the name of the UID table as a byte array for client requests */ public byte[] uidTable() { return this.uidtable; diff --git a/src/tsd/HttpJsonSerializer.java b/src/tsd/HttpJsonSerializer.java index 57fcf1b567..67211e0c67 100644 --- a/src/tsd/HttpJsonSerializer.java +++ b/src/tsd/HttpJsonSerializer.java @@ -192,6 +192,26 @@ public HashMap> parseUidAssignV1() { } } + /** + * Parses metric, tagk or tagv, and name to rename UID + * @return as hash map of type and name + * @throws JSONException if parsing failed + * @throws BadRequestException if the content was missing or parsing failed + */ + public HashMap parseUidRenameV1() { + final String json = query.getContent(); + if (json == null || json.isEmpty()) { + throw new BadRequestException(HttpResponseStatus.BAD_REQUEST, + "Missing message content", + "Supply valid JSON formatted data in the body of your request"); + } + try { + return JSON.parseToObject(json, TR_HASH_MAP); + } catch (IllegalArgumentException iae) { + throw new BadRequestException("Unable to parse the given JSON", iae); + } + } + /** * Parses a timeseries data query * @return A TSQuery with data ready to validate @@ -533,6 +553,15 @@ public ChannelBuffer formatUidAssignV1(final return this.serializeJSON(response); } + /** + * Format a response from the Uid Rename RPC + * @param response A map of result and error of the rename + * @return A JSON structure + * @throws JSONException if serialization failed + */ + public ChannelBuffer formatUidRenameV1(final Map response) { + return this.serializeJSON(response); + } /** * Format the results from a timeseries data query * @param data_query The TSQuery object used to fetch the results diff --git a/src/tsd/HttpSerializer.java b/src/tsd/HttpSerializer.java index 8f16ea9590..8078c6ecfd 100644 --- a/src/tsd/HttpSerializer.java +++ b/src/tsd/HttpSerializer.java @@ -198,6 +198,18 @@ public HashMap> parseUidAssignV1() { " has not implemented parseUidAssignV1"); } + /** + * Parses metrics, tagk or tagvs type and name to rename UID + * @return as hash map of type and name + * @throws BadRequestException if the plugin has not implemented this method + */ + public HashMap parseUidRenameV1() { + throw new BadRequestException(HttpResponseStatus.NOT_IMPLEMENTED, + "The requested API endpoint has not been implemented", + this.getClass().getCanonicalName() + + " has not implemented parseUidRenameV1"); + } + /** * Parses a SearchQuery request * @return The parsed search query @@ -443,6 +455,19 @@ public ChannelBuffer formatUidAssignV1(final " has not implemented formatUidAssignV1"); } + /** + * Format a response from the Uid Rename RPC + * @param response A map of result and reason for error of the rename + * @return A ChannelBuffer object to pass on to the caller + * @throws BadRequestException if the plugin has not implemented this method + */ + public ChannelBuffer formatUidRenameV1(final Map response) { + throw new BadRequestException(HttpResponseStatus.NOT_IMPLEMENTED, + "The requested API endpoint has not been implemented", + this.getClass().getCanonicalName() + + " has not implemented formatUidRenameV1"); + } + /** * Format the results from a timeseries data query * @param query The TSQuery object used to fetch the results diff --git a/src/tsd/UniqueIdRpc.java b/src/tsd/UniqueIdRpc.java index 1747e3f188..0318d84b7d 100644 --- a/src/tsd/UniqueIdRpc.java +++ b/src/tsd/UniqueIdRpc.java @@ -62,6 +62,9 @@ public void execute(TSDB tsdb, HttpQuery query) throws IOException { } else if (endpoint.toLowerCase().equals("tsmeta")) { this.handleTSMeta(tsdb, query); return; + } else if (endpoint.toLowerCase().equals("rename")) { + this.handleRename(tsdb, query); + return; } else { throw new BadRequestException(HttpResponseStatus.NOT_IMPLEMENTED, "Other UID endpoints have not been implemented yet"); @@ -477,6 +480,70 @@ private UIDMeta parseUIDMetaQS(final HttpQuery query) { return meta; } + /** + * Rename UID to a new name of the given metric, tagk or tagv names + *

+ * This handler supports GET and POST whereby the GET command can parse query + * strings with the {@code type} and {@code name} as their parameters. + *

+ * @param tsdb The TSDB from the RPC router + * @param query The query for this request + */ + private void handleRename(final TSDB tsdb, final HttpQuery query) { + // only accept GET and POST + if (query.method() != HttpMethod.GET && query.method() != HttpMethod.POST) { + throw new BadRequestException(HttpResponseStatus.METHOD_NOT_ALLOWED, + "Method not allowed", "The HTTP method[" + query.method().getName() + + "] is not permitted for this endpoint"); + } + + final HashMap source; + if (query.method() == HttpMethod.POST) { + source = query.serializer().parseUidRenameV1(); + } else { + source = new HashMap(3); + final String[] types = {"metric", "tagk", "tagv", "name"}; + for (int i = 0; i < types.length; i++) { + final String value = query.getQueryStringParam(types[i]); + if (value!= null && !value.isEmpty()) { + source.put(types[i], value); + } + } + } + String type = null; + String oldname = null; + String newname = null; + for (Map.Entry entry : source.entrySet()) { + if (entry.getKey().equals("name")) { + newname = entry.getValue(); + } else { + type = entry.getKey(); + oldname = entry.getValue(); + } + } + + // we need a type/value and new name + if (type == null || oldname == null || newname == null) { + throw new BadRequestException("Missing necessary values to rename UID"); + } + + HashMap response = new HashMap(2); + try { + tsdb.renameUid(type, oldname, newname); + response.put("result", "true"); + } catch (IllegalArgumentException e) { + response.put("result", "false"); + response.put("error", e.getMessage()); + } + + if (!response.containsKey("error")) { + query.sendReply(query.serializer().formatUidRenameV1(response)); + } else { + query.sendReply(HttpResponseStatus.BAD_REQUEST, + query.serializer().formatUidRenameV1(response)); + } + } + /** * Used with verb overrides to parse out values from a query string * @param query The query to parse diff --git a/src/uid/UniqueId.java b/src/uid/UniqueId.java index e94c988648..ea443da2e7 100644 --- a/src/uid/UniqueId.java +++ b/src/uid/UniqueId.java @@ -15,10 +15,13 @@ import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.xml.bind.DatatypeConverter; @@ -104,6 +107,9 @@ public enum UniqueIdType { /** Map of pending UID assignments */ private final HashMap> pending_assignments = new HashMap>(); + /** Set of UID rename */ + private final Set renaming_id_names = + Collections.synchronizedSet(new HashSet()); /** Number of times we avoided reading from HBase thanks to the cache. */ private volatile int cache_hits; @@ -869,6 +875,7 @@ public Object call(Object ignored) throws Exception { */ public void rename(final String oldname, final String newname) { final byte[] row = getId(oldname); + final String row_string = fromBytes(row); { byte[] id = null; try { @@ -883,6 +890,15 @@ public void rename(final String oldname, final String newname) { } } + if (renaming_id_names.contains(row_string) + || renaming_id_names.contains(newname)) { + throw new IllegalArgumentException("Ongoing rename on the same ID(\"" + + Arrays.toString(row) + "\") or an identical new name(\"" + newname + + "\")"); + } + renaming_id_names.add(row_string); + renaming_id_names.add(newname); + final byte[] newnameb = toBytes(newname); // Update the reverse mapping first, so that if we die before updating @@ -898,6 +914,8 @@ public void rename(final String oldname, final String newname) { LOG.error("When trying rename(\"" + oldname + "\", \"" + newname + "\") on " + this + ": Failed to update reverse" + " mapping for ID=" + Arrays.toString(row), e); + renaming_id_names.remove(row_string); + renaming_id_names.remove(newname); throw e; } @@ -911,6 +929,8 @@ public void rename(final String oldname, final String newname) { LOG.error("When trying rename(\"" + oldname + "\", \"" + newname + "\") on " + this + ": Failed to create the" + " new forward mapping with ID=" + Arrays.toString(row), e); + renaming_id_names.remove(row_string); + renaming_id_names.remove(newname); throw e; } @@ -935,6 +955,9 @@ public void rename(final String oldname, final String newname) { + " old forward mapping for ID=" + Arrays.toString(row); LOG.error("WTF? " + msg, e); throw new RuntimeException(msg, e); + } finally { + renaming_id_names.remove(row_string); + renaming_id_names.remove(newname); } // Success! } diff --git a/test/core/TestTSDB.java b/test/core/TestTSDB.java index ae3fc98230..91a97ec96e 100644 --- a/test/core/TestTSDB.java +++ b/test/core/TestTSDB.java @@ -401,6 +401,52 @@ public void assignUidInvalidCharacter() { tsdb.assignUid("metric", "Not!A:Valid@Name"); } + @Test (expected = IllegalArgumentException.class) + public void renameUidInvalidNewname() { + tsdb.renameUid("metric", "existing", null); + } + + @Test (expected = IllegalArgumentException.class) + public void renameUidNonexistentMetric() { + when(metrics.getId("sys.cpu.1")).thenThrow( + new NoSuchUniqueName("metric", "sys.cpu.1")); + tsdb.renameUid("metric", "sys.cpu.1", "sys.cpu.2"); + } + + @Test + public void renameUidMetric() { + tsdb.renameUid("metric", "sys.cpu.1", "sys.cpu.2"); + } + + @Test (expected = IllegalArgumentException.class) + public void renameUidNonexistentTagk() { + when(tag_names.getId("datacenter")).thenThrow( + new NoSuchUniqueName("tagk", "datacenter")); + tsdb.renameUid("tagk", "datacenter", "datacluster"); + } + + @Test + public void renameUidTagk() { + tsdb.renameUid("tagk", "datacenter", "datacluster"); + } + + @Test (expected = IllegalArgumentException.class) + public void renameUidNonexistentTagv() { + when(tag_values.getId("localhost")).thenThrow( + new NoSuchUniqueName("tagv", "localhost")); + tsdb.renameUid("tagv", "localhost", "127.0.0.1"); + } + + @Test + public void renameUidTagv() { + tsdb.renameUid("tagv", "localhost", "127.0.0.1"); + } + + @Test (expected = IllegalArgumentException.class) + public void renameUidBadType() { + tsdb.renameUid("wrongtype", METRIC_STRING, METRIC_STRING); + } + @Test public void uidTable() { assertNotNull(tsdb.uidTable()); diff --git a/test/tsd/TestHttpJsonSerializer.java b/test/tsd/TestHttpJsonSerializer.java index a4669a87b1..70feb5ceb2 100644 --- a/test/tsd/TestHttpJsonSerializer.java +++ b/test/tsd/TestHttpJsonSerializer.java @@ -163,6 +163,37 @@ public void parseSuggestV1NotJSON() throws Exception { serdes.parseSuggestV1(); } + @Test + public void parseUidRenameV1() throws Exception { + HttpQuery query = NettyMocks.postQuery(tsdb, "", + "{\"metric\":\"sys.cpu.1\",\"name\":\"sys.cpu.2\"}", ""); + HttpJsonSerializer serdes = new HttpJsonSerializer(query); + HashMap map = serdes.parseUidRenameV1(); + assertNotNull(map); + assertEquals("sys.cpu.1", map.get("metric")); + } + + @Test (expected = BadRequestException.class) + public void parseUidRenameV1NoContent() throws Exception { + HttpQuery query = NettyMocks.postQuery(tsdb, "", null, ""); + HttpJsonSerializer serdes = new HttpJsonSerializer(query); + serdes.parseUidRenameV1(); + } + + @Test (expected = BadRequestException.class) + public void parseUidRenameV1EmptyContent() throws Exception { + HttpQuery query = NettyMocks.postQuery(tsdb, "", "", ""); + HttpJsonSerializer serdes = new HttpJsonSerializer(query); + serdes.parseUidRenameV1(); + } + + @Test (expected = BadRequestException.class) + public void parseUidRenameV1NotJSON() throws Exception { + HttpQuery query = NettyMocks.postQuery(tsdb, "", "NOT JSON", ""); + HttpJsonSerializer serdes = new HttpJsonSerializer(query); + serdes.parseUidRenameV1(); + } + @Test public void formatSuggestV1() throws Exception { HttpQuery query = NettyMocks.getQuery(tsdb, ""); @@ -194,6 +225,38 @@ public void formatSuggestV1Null() throws Exception { serdes.formatSuggestV1(null); } + @Test + public void formatUidRenameV1Success() throws Exception { + HttpQuery query = NettyMocks.getQuery(tsdb, ""); + HttpJsonSerializer serdes = new HttpJsonSerializer(query); + final HashMap map = new HashMap(2); + map.put("result", "true"); + ChannelBuffer cb = serdes.formatUidRenameV1(map); + assertNotNull(cb); + assertEquals("{\"result\":\"true\"}", + cb.toString(Charset.forName("UTF-8"))); + } + + @Test + public void formatUidRenameV1Failed() throws Exception { + HttpQuery query = NettyMocks.getQuery(tsdb, ""); + HttpJsonSerializer serdes = new HttpJsonSerializer(query); + final HashMap map = new HashMap(2); + map.put("result", "false"); + map.put("error", "known"); + ChannelBuffer cb = serdes.formatUidRenameV1(map); + assertNotNull(cb); + assertEquals("{\"error\":\"known\",\"result\":\"false\"}", + cb.toString(Charset.forName("UTF-8"))); + } + + @Test (expected = IllegalArgumentException.class) + public void formatUidRenameV1Null() throws Exception { + HttpQuery query = NettyMocks.getQuery(tsdb, ""); + HttpJsonSerializer serdes = new HttpJsonSerializer(query); + serdes.formatUidRenameV1(null); + } + @Test public void formatSerializersV1() throws Exception { HttpQuery.initializeSerializerMaps(tsdb); diff --git a/test/tsd/TestUniqueIdRpc.java b/test/tsd/TestUniqueIdRpc.java index 1e7c5240b6..46b72e3a59 100644 --- a/test/tsd/TestUniqueIdRpc.java +++ b/test/tsd/TestUniqueIdRpc.java @@ -14,6 +14,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.when; import static org.powermock.api.mockito.PowerMockito.mock; @@ -505,6 +506,157 @@ public void stringToUniqueIdTypeEmpty() throws Exception { UniqueId.stringToUniqueIdType("Not a type"); } + // Test /api/uid/rename ---------------------- + + @Test (expected = BadRequestException.class) + public void renameBadMethod() throws Exception { + HttpQuery query = NettyMocks.putQuery(tsdb, "/api/uid/rename", ""); + rpc.execute(tsdb, query); + } + + @Test + public void renamePostMetric() throws Exception { + HttpQuery query = NettyMocks.postQuery(tsdb, "/api/uid/rename", + "{\"metric\":\"sys.cpu.1\",\"name\":\"sys.cpu.2\"}"); + rpc.execute(tsdb, query); + assertEquals(HttpResponseStatus.OK, query.response().getStatus()); + assertEquals("{\"result\":\"true\"}", + query.response().getContent().toString(Charset.forName("UTF-8"))); + } + + @Test + public void renamePostTagk() throws Exception { + HttpQuery query = NettyMocks.postQuery(tsdb, "/api/uid/rename", + "{\"tagk\":\"datacenter\",\"name\":\"datacluster\"}"); + rpc.execute(tsdb, query); + assertEquals(HttpResponseStatus.OK, query.response().getStatus()); + assertEquals("{\"result\":\"true\"}", + query.response().getContent().toString(Charset.forName("UTF-8"))); + } + + @Test + public void renamePostTagv() throws Exception { + HttpQuery query = NettyMocks.postQuery(tsdb, "/api/uid/rename", + "{\"tagv\":\"localhost\",\"name\":\"127.0.0.1\"}"); + rpc.execute(tsdb, query); + assertEquals(HttpResponseStatus.OK, query.response().getStatus()); + assertEquals("{\"result\":\"true\"}", + query.response().getContent().toString(Charset.forName("UTF-8"))); + } + + @Test (expected = BadRequestException.class) + public void renamePostNoName() throws Exception { + HttpQuery query = NettyMocks.postQuery(tsdb, "/api/uid/rename", + "{\"tagk\":\"localhost\",\"not_name\":\"127.0.0.1\"}"); + rpc.execute(tsdb, query); + } + + @Test (expected = BadRequestException.class) + public void renamePostNoType() throws Exception { + HttpQuery query = NettyMocks.postQuery(tsdb, "/api/uid/rename", + "{\"name\":\"127.0.0.1\"}"); + rpc.execute(tsdb, query); + } + + @Test (expected = BadRequestException.class) + public void renamePostNotJSON() throws Exception { + HttpQuery query = NettyMocks.postQuery(tsdb, "/api/uid/rename", "Not JSON"); + rpc.execute(tsdb, query); + } + + @Test (expected = BadRequestException.class) + public void renamePostZeroLengthContent() throws Exception { + HttpQuery query = NettyMocks.postQuery(tsdb, "/api/uid/rename", ""); + rpc.execute(tsdb, query); + } + + @Test (expected = BadRequestException.class) + public void renamePostEmptyJSON() throws Exception { + HttpQuery query = NettyMocks.postQuery(tsdb, "/api/uid/rename", "{}"); + rpc.execute(tsdb, query); + } + + @Test + public void renameQsMetric() throws Exception { + HttpQuery query = NettyMocks.getQuery(tsdb, + "/api/uid/rename?metric=sys.cpu.1&name=sys.cpu.2"); + rpc.execute(tsdb, query); + assertEquals(HttpResponseStatus.OK, query.response().getStatus()); + assertEquals("{\"result\":\"true\"}", + query.response().getContent().toString(Charset.forName("UTF-8"))); + } + + @Test + public void renameQsTagk() throws Exception { + HttpQuery query = NettyMocks.getQuery(tsdb, + "/api/uid/rename?tagk=datacenter&name=datacluster"); + rpc.execute(tsdb, query); + assertEquals(HttpResponseStatus.OK, query.response().getStatus()); + assertEquals("{\"result\":\"true\"}", + query.response().getContent().toString(Charset.forName("UTF-8"))); + } + + @Test + public void renameQsTagv() throws Exception { + HttpQuery query = NettyMocks.getQuery(tsdb, + "/api/uid/rename?tagv=localhost&name=127.0.0.1"); + rpc.execute(tsdb, query); + assertEquals(HttpResponseStatus.OK, query.response().getStatus()); + assertEquals("{\"result\":\"true\"}", + query.response().getContent().toString(Charset.forName("UTF-8"))); + } + + @Test + public void renameQsSkipUnsupportedParam() throws Exception { + HttpQuery query = NettyMocks.getQuery(tsdb, + "/api/uid/rename?tagv=localhost&name=127.0.0.1&drop=db"); + rpc.execute(tsdb, query); + assertEquals(HttpResponseStatus.OK, query.response().getStatus()); + assertEquals("{\"result\":\"true\"}", + query.response().getContent().toString(Charset.forName("UTF-8"))); + } + + @Test (expected = BadRequestException.class) + public void renameQsMissingType() throws Exception { + HttpQuery query = NettyMocks.getQuery(tsdb, + "/api/uid/rename?name=127.0.0.1"); + rpc.execute(tsdb, query); + } + + @Test (expected = BadRequestException.class) + public void renameQsMissingName() throws Exception { + HttpQuery query = NettyMocks.getQuery(tsdb, + "/api/uid/rename?metric=sys.cpu.1"); + rpc.execute(tsdb, query); + } + + @Test (expected = BadRequestException.class) + public void renameQsNoParamValue() throws Exception { + HttpQuery query = NettyMocks.getQuery(tsdb, + "/api/uid/rename?metric=&name=sys.cpu.2"); + rpc.execute(tsdb, query); + } + + @Test (expected = BadRequestException.class) + public void renameQsNoParam() throws Exception { + HttpQuery query = NettyMocks.getQuery(tsdb, + "/api/uid/rename?"); + rpc.execute(tsdb, query); + } + + @Test + public void renameRenameException() throws Exception { + final String message = "New name already exists"; + doThrow(new IllegalArgumentException(message)).when(tsdb).renameUid("tagv", + "localhost", "localhost"); + HttpQuery query = NettyMocks.getQuery(tsdb, + "/api/uid/rename?tagv=localhost&name=localhost"); + rpc.execute(tsdb, query); + assertEquals(HttpResponseStatus.BAD_REQUEST, query.response().getStatus()); + assertEquals("{\"error\":\"" + message + "\",\"result\":\"false\"}", + query.response().getContent().toString(Charset.forName("UTF-8"))); + } + // Teset /api/uid/uidmeta -------------------- @Test diff --git a/test/uid/TestUniqueId.java b/test/uid/TestUniqueId.java index a35aeeae3c..e6a81dfe92 100644 --- a/test/uid/TestUniqueId.java +++ b/test/uid/TestUniqueId.java @@ -27,6 +27,7 @@ import org.hbase.async.AtomicIncrementRequest; import org.hbase.async.Bytes; +import org.hbase.async.DeleteRequest; import org.hbase.async.GetRequest; import org.hbase.async.HBaseClient; import org.hbase.async.HBaseException; @@ -1062,6 +1063,82 @@ public void longToUIDTooBig() throws Exception { UniqueId.longToUID(257, (short)1); } + @Test + public void rename() throws Exception { + uid = new UniqueId(client, table, METRIC, 3); + final byte[] foo_id = { 0, 'a', 0x42 }; + final byte[] foo_name = { 'f', 'o', 'o' }; + + ArrayList kvs = new ArrayList(1); + kvs.add(new KeyValue(foo_name, ID, METRIC_ARRAY, foo_id)); + when(client.get(anyGet())) + .thenReturn(Deferred.fromResult(kvs)) + .thenReturn(Deferred.>fromResult(null)); + when(client.put(anyPut())).thenAnswer(answerTrue()); + when(client.delete(anyDelete())).thenAnswer(answerTrue()); + + uid.rename("foo", "bar"); + } + + @Test (expected = IllegalArgumentException.class) + public void renameNewNameExists() throws Exception { + uid = new UniqueId(client, table, METRIC, 3); + final byte[] foo_id = { 0, 'a', 0x42 }; + final byte[] foo_name = { 'f', 'o', 'o' }; + final byte[] bar_id = { 1, 'b', 0x43 }; + final byte[] bar_name = { 'b', 'a', 'r' }; + + ArrayList foo_kvs = new ArrayList(1); + ArrayList bar_kvs = new ArrayList(1); + foo_kvs.add(new KeyValue(foo_name, ID, METRIC_ARRAY, foo_id)); + bar_kvs.add(new KeyValue(bar_name, ID, METRIC_ARRAY, bar_id)); + when(client.get(anyGet())) + .thenReturn(Deferred.fromResult(foo_kvs)) + .thenReturn(Deferred.fromResult(bar_kvs)); + when(client.put(anyPut())).thenAnswer(answerTrue()); + when(client.delete(anyDelete())).thenAnswer(answerTrue()); + + uid.rename("foo", "bar"); + } + + @Test (expected = IllegalStateException.class) + public void renameRaceCondition() throws Exception { + // Simulate a race between client A(default) and client B. + // A and B rename same UID to different name. + // B waits till A start to invoke PutRequest to start. + + uid = new UniqueId(client, table, METRIC, 3); + HBaseClient client_b = mock(HBaseClient.class); + final UniqueId uid_b = new UniqueId(client_b, table, METRIC, 3); + + final byte[] foo_id = { 0, 'a', 0x42 }; + final byte[] foo_name = { 'f', 'o', 'o' }; + + ArrayList kvs = new ArrayList(1); + kvs.add(new KeyValue(foo_name, ID, METRIC_ARRAY, foo_id)); + + when(client_b.get(anyGet())) + .thenReturn(Deferred.fromResult(kvs)) + .thenReturn(Deferred.>fromResult(null)); + when(client_b.put(anyPut())).thenAnswer(answerTrue()); + when(client_b.delete(anyDelete())).thenAnswer(answerTrue()); + + final Answer> the_race = new Answer>() { + public Deferred answer(final InvocationOnMock inv) throws Exception { + uid_b.rename("foo", "xyz"); + return Deferred.fromResult(true); + } + }; + + when(client.get(anyGet())) + .thenReturn(Deferred.fromResult(kvs)) + .thenReturn(Deferred.>fromResult(null)); + when(client.put(anyPut())).thenAnswer(the_race); + when(client.delete(anyDelete())).thenAnswer(answerTrue()); + + uid.rename("foo", "bar"); + } + @Test public void deleteCached() throws Exception { setupStorage(); @@ -1220,6 +1297,18 @@ private static PutRequest anyPut() { return any(PutRequest.class); } + private static DeleteRequest anyDelete() { + return any(DeleteRequest.class); + } + + private static Answer> answerTrue() { + return new Answer>() { + public Deferred answer(final InvocationOnMock inv) { + return Deferred.fromResult(true); + } + }; + } + @SuppressWarnings("unchecked") private static Callback> anyByteCB() { return any(Callback.class); From f84ef2a09d5515eb84b429cc74df32d542a976ab Mon Sep 17 00:00:00 2001 From: Christos Soulios Date: Wed, 28 Oct 2015 12:07:32 -0700 Subject: [PATCH 004/233] Autoconf bigtable Added build support for bigtable data store using autotools dependencies. Script build-bigtable.sh performs builds using asyncbigtable library as a dependency. Signed-off-by: Chris Larsen --- Makefile.am | 21 ++++- build-bigtable.sh | 9 ++ configure.ac | 10 ++ pom.xml.in | 92 ++++++++++++------- .../alpn-boot-7.1.3.v20150130.jar.md5 | 1 + third_party/alpn-boot/include.mk | 23 +++++ ...027.224128-1-jar-with-dependencies.jar.md5 | 1 + third_party/asyncbigtable/include.mk | 23 +++++ third_party/include.mk | 15 ++- tsdb.in | 17 +++- 10 files changed, 174 insertions(+), 38 deletions(-) create mode 100644 build-bigtable.sh create mode 100644 third_party/alpn-boot/alpn-boot-7.1.3.v20150130.jar.md5 create mode 100644 third_party/alpn-boot/include.mk create mode 100644 third_party/asyncbigtable/asyncbigtable-0.2.0-20151027.224128-1-jar-with-dependencies.jar.md5 create mode 100644 third_party/asyncbigtable/include.mk diff --git a/Makefile.am b/Makefile.am index f9395a0504..56c92b14b4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -153,7 +153,6 @@ tsdb_SRC := \ src/utils/Threads.java tsdb_DEPS = \ - $(ASYNCHBASE) \ $(GUAVA) \ $(LOG4J_OVER_SLF4J) \ $(LOGBACK_CLASSIC) \ @@ -162,12 +161,25 @@ tsdb_DEPS = \ $(JACKSON_CORE) \ $(JACKSON_DATABIND) \ $(NETTY) \ - $(PROTOBUF) \ $(SLF4J_API) \ $(SUASYNC) \ - $(ZOOKEEPER) \ $(APACHE_MATH) +if BIGTABLE +tsdb_DEPS += \ + $(ALPN_BOOT) \ + $(ASYNCBIGTABLE) +maven_profile_bigtable := true +maven_profile_hbase := false +else +tsdb_DEPS += \ + $(ASYNCHBASE) \ + $(PROTOBUF) \ + $(ZOOKEEPER) +maven_profile_bigtable := false +maven_profile_hbase := true +endif + test_SRC := \ test/core/SeekableViewsForTest.java \ test/core/BaseTsdbTest.java \ @@ -664,6 +676,7 @@ pom.xml: pom.xml.in Makefile echo ''; \ sed <$< \ -e 's/@ASYNCHBASE_VERSION@/$(ASYNCHBASE_VERSION)/' \ + -e 's/@ASYNCBIGTABLE_VERSION@/$(ASYNCBIGTABLE_VERSION)/' \ -e 's/@GUAVA_VERSION@/$(GUAVA_VERSION)/' \ -e 's/@GWT_VERSION@/$(GWT_VERSION)/' \ -e 's/@HAMCREST_VERSION@/$(HAMCREST_VERSION)/' \ @@ -684,6 +697,8 @@ pom.xml: pom.xml.in Makefile -e 's/@spec_title@/$(spec_title)/' \ -e 's/@spec_vendor@/$(spec_vendor)/' \ -e 's/@spec_version@/$(PACKAGE_VERSION)/' \ + -e 's/@maven_profile_hbase@/$(maven_profile_hbase)/' \ + -e 's/@maven_profile_bigtable@/$(maven_profile_bigtable)/' \ ; \ } >$@-t mv $@-t ../$@ diff --git a/build-bigtable.sh b/build-bigtable.sh new file mode 100644 index 0000000000..1076da2f8e --- /dev/null +++ b/build-bigtable.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -xe +test -f configure || ./bootstrap +test -d build || mkdir build +cd build +test -f Makefile || ../configure --with-bigtable "$@" +MAKE=make +[ `uname -s` = "FreeBSD" ] && MAKE=gmake +exec ${MAKE} "$@" \ No newline at end of file diff --git a/configure.ac b/configure.ac index 0b8dcdd76e..dea9bd6147 100644 --- a/configure.ac +++ b/configure.ac @@ -24,6 +24,16 @@ AC_CONFIG_FILES([ AC_CONFIG_FILES([opentsdb.spec]) AC_CONFIG_FILES([build-aux/fetchdep.sh], [chmod +x build-aux/fetchdep.sh]) +AC_ARG_WITH([bigtable], + [AS_HELP_STRING([--with-bigtable], [enable bigtable backend])], + [with_bigtable=yes], + [with_bigtable=no]) + +AS_IF([test "x$with_bigtable" = "xyes"], + [AM_CONDITIONAL(BIGTABLE, true)], + [AM_CONDITIONAL(BIGTABLE, false)] +) + TSDB_FIND_PROG([md5], [md5sum md5 gmd5sum digest]) if test x`basename "$MD5"` = x'digest'; then MD5='digest -a md5' diff --git a/pom.xml.in b/pom.xml.in index 9539eaa2a1..fee38d15af 100644 --- a/pom.xml.in +++ b/pom.xml.in @@ -355,42 +355,12 @@ @SUASYNC_VERSION@ - - org.apache.zookeeper - zookeeper - @ZOOKEEPER_VERSION@ - - - log4j - log4j - - - org.slf4j - slf4j-log4j12 - - - jline - jline - - - junit - junit - - - - org.slf4j slf4j-api @SLF4J_API_VERSION@ - - org.hbase - asynchbase - @ASYNCHBASE_VERSION@ - - org.apache.commons commons-math3 @@ -481,7 +451,67 @@ UTF-8 - + + + + + hbase + + @maven_profile_hbase@ + + + + + org.hbase + asynchbase + @ASYNCHBASE_VERSION@ + + + + org.apache.zookeeper + zookeeper + @ZOOKEEPER_VERSION@ + + + log4j + log4j + + + org.slf4j + slf4j-log4j12 + + + jline + jline + + + junit + junit + + + + + + + + + bigtable + + @maven_profile_bigtable@ + + + + + com.pythian.opentsdb + asyncbigtable + @ASYNCBIGTABLE_VERSION@ + jar-with-dependencies + + + + + + org.sonatype.oss oss-parent diff --git a/third_party/alpn-boot/alpn-boot-7.1.3.v20150130.jar.md5 b/third_party/alpn-boot/alpn-boot-7.1.3.v20150130.jar.md5 new file mode 100644 index 0000000000..b51d10325d --- /dev/null +++ b/third_party/alpn-boot/alpn-boot-7.1.3.v20150130.jar.md5 @@ -0,0 +1 @@ +b10366c9301e954bcedbf9130b6381c7 diff --git a/third_party/alpn-boot/include.mk b/third_party/alpn-boot/include.mk new file mode 100644 index 0000000000..d7ead1358d --- /dev/null +++ b/third_party/alpn-boot/include.mk @@ -0,0 +1,23 @@ +# Copyright (C) 2015 The OpenTSDB Authors. +# +# This library is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 2.1 of the License, or +# (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . + +ALPN_BOOT_VERSION := 7.1.3.v20150130 +ALPN_BOOT := third_party/alpn-boot/alpn-boot-$(ALPN_BOOT_VERSION).jar +ALBPN_BOOT_BASE_URL := http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/$(ALPN_BOOT_VERSION) + +$(ALPN_BOOT): $(ALPN_BOOT).md5 + set dummy "$(ALBPN_BOOT_BASE_URL)" "$(ALPN_BOOT)"; shift; $(FETCH_DEPENDENCY) + +THIRD_PARTY += $(ALPN_BOOT) diff --git a/third_party/asyncbigtable/asyncbigtable-0.2.0-20151027.224128-1-jar-with-dependencies.jar.md5 b/third_party/asyncbigtable/asyncbigtable-0.2.0-20151027.224128-1-jar-with-dependencies.jar.md5 new file mode 100644 index 0000000000..ef206ef18b --- /dev/null +++ b/third_party/asyncbigtable/asyncbigtable-0.2.0-20151027.224128-1-jar-with-dependencies.jar.md5 @@ -0,0 +1 @@ +ef53c7422ee741867be4e925484f8d88 \ No newline at end of file diff --git a/third_party/asyncbigtable/include.mk b/third_party/asyncbigtable/include.mk new file mode 100644 index 0000000000..e9eca08073 --- /dev/null +++ b/third_party/asyncbigtable/include.mk @@ -0,0 +1,23 @@ +# Copyright (C) 2015 The OpenTSDB Authors. +# +# This library is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 2.1 of the License, or +# (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . + +ASYNCBIGTABLE_VERSION := 0.2.0-20151027.224128-1 +ASYNCBIGTABLE := third_party/asyncbigtable/asyncbigtable-$(ASYNCBIGTABLE_VERSION)-jar-with-dependencies.jar +ASYNCBIGTABLE_BASE_URL := https://oss.sonatype.org/content/repositories/snapshots/com/pythian/opentsdb/asyncbigtable/0.2.0-SNAPSHOT/ + +$(ASYNCBIGTABLE): $(ASYNCBIGTABLE).md5 + set dummy "$(ASYNCBIGTABLE_BASE_URL)" "$(ASYNCBIGTABLE)"; shift; $(FETCH_DEPENDENCY) + +THIRD_PARTY += $(ASYNCBIGTABLE) \ No newline at end of file diff --git a/third_party/include.mk b/third_party/include.mk index 56649734d1..ab92e5b4fe 100644 --- a/third_party/include.mk +++ b/third_party/include.mk @@ -21,7 +21,6 @@ THIRD_PARTY = include third_party/guava/include.mk include third_party/gwt/include.mk include third_party/hamcrest/include.mk -include third_party/hbase/include.mk include third_party/jackson/include.mk include third_party/javassist/include.mk include third_party/junit/include.mk @@ -30,9 +29,19 @@ include third_party/mockito/include.mk include third_party/netty/include.mk include third_party/objenesis/include.mk include third_party/powermock/include.mk -include third_party/protobuf/include.mk include third_party/slf4j/include.mk include third_party/suasync/include.mk include third_party/validation-api/include.mk -include third_party/zookeeper/include.mk include third_party/apache/include.mk + +if BIGTABLE +include third_party/alpn-boot/include.mk +include third_party/asyncbigtable/include.mk +ASYNCHBASE_VERSION = 0.0 +ZOOKEEPER_VERSION = 0.0 +else +include third_party/hbase/include.mk +include third_party/protobuf/include.mk +include third_party/zookeeper/include.mk +ASYNCBIGTABLE_VERSION = 0.0 +endif \ No newline at end of file diff --git a/tsdb.in b/tsdb.in index d96c53d540..8eaf5d6c28 100644 --- a/tsdb.in +++ b/tsdb.in @@ -106,4 +106,19 @@ shift JAVA=${JAVA-'java'} JVMARGS=${JVMARGS-'-enableassertions -enablesystemassertions'} test -r "$localdir/tsdb.local" && . "$localdir/tsdb.local" -exec $JAVA $JVMARGS -classpath "$CLASSPATH" net.opentsdb.tools.$MAINCLASS "$@" + +if [[ $CLASSPATH == *"asyncbigtable"* ]] +then + USE_BIGTABLE=1 + echo "Running OpenTSDB with Bigtable support" + + test -n "$HBASE_CONF" || { + echo >&2 'The environment variable HBASE_CONF must be set' + exit 1 + } + + ALPN_BOOT_JAR=$(find $localdir -name alpn-boot\*.jar) + exec $JAVA $JVMARGS -classpath "$CLASSPATH:$HBASE_CONF" -Xbootclasspath/p:$ALPN_BOOT_JAR net.opentsdb.tools.$MAINCLASS "$@" +else + exec $JAVA $JVMARGS -classpath "$CLASSPATH" net.opentsdb.tools.$MAINCLASS "$@" +fi From 108f23ed8e28e6325ece0a70322e12a9b423fbdf Mon Sep 17 00:00:00 2001 From: Hong Dai Thanh Date: Wed, 28 Oct 2015 10:22:31 +0700 Subject: [PATCH 005/233] s/space/comma/ in instruction for specifying ZooKeeper quorum Signed-off-by: Chris Larsen --- build-aux/rpm/opentsdb.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-aux/rpm/opentsdb.conf b/build-aux/rpm/opentsdb.conf index 11f66ca6cf..a515418a7e 100644 --- a/build-aux/rpm/opentsdb.conf +++ b/build-aux/rpm/opentsdb.conf @@ -58,6 +58,6 @@ tsd.core.plugin_path = /usr/share/opentsdb/plugins # Path under which the znode for the -ROOT- region is located, default is "/hbase" #tsd.storage.hbase.zk_basedir = /hbase -# A space separated list of Zookeeper hosts to connect to, with or without +# A comma separated list of Zookeeper hosts to connect to, with or without # port specifiers, default is "localhost" #tsd.storage.hbase.zk_quorum = localhost From 2cce79a2ba03cbbe56964e1b2e86978c06700888 Mon Sep 17 00:00:00 2001 From: Yubao Liu Date: Fri, 23 Oct 2015 22:36:08 +0800 Subject: [PATCH 006/233] remove debug log with wrong INFO level to save disk space Signed-off-by: Chris Larsen --- src/meta/TSMeta.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/meta/TSMeta.java b/src/meta/TSMeta.java index 765ac0eb16..7c34cbfc2a 100644 --- a/src/meta/TSMeta.java +++ b/src/meta/TSMeta.java @@ -525,7 +525,6 @@ final class TSMetaCB implements Callback, Long> { @Override public Deferred call(final Long incremented_value) throws Exception { -LOG.info("Value: " + incremented_value); if (incremented_value > 1) { // TODO - maybe update the search index every X number of increments? // Otherwise the search engine would only get last_updated/count From 68c7b5633dd1dc57bf054840082d0e22457bf90a Mon Sep 17 00:00:00 2001 From: Jim Westfall Date: Mon, 19 Oct 2015 17:05:58 -0700 Subject: [PATCH 007/233] QueryUi: URL.decode() url/query string before use Some browsers (aka firefox) like to encode { and } as %7B and %7D. This causes problem when parsing the query string since its using { and } to figure out the metric and tags. Without this the UI thows an error like the following: Request failed: Bad Request: No such name for 'metrics': 'server.nic.usage.mbit%7Bhost=host1%7D' Signed-off-by: Chris Larsen --- src/tsd/client/QueryUi.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tsd/client/QueryUi.java b/src/tsd/client/QueryUi.java index 4e2eccdd9d..cf78c6feca 100644 --- a/src/tsd/client/QueryUi.java +++ b/src/tsd/client/QueryUi.java @@ -780,7 +780,7 @@ private static QueryString getQueryString(final String qs) { } private void refreshFromQueryString() { - final QueryString qs = getQueryString(History.getToken()); + final QueryString qs = getQueryString(URL.decode(History.getToken())); maybeSetTextbox(qs, "start", start_datebox.getTextBox()); maybeSetTextbox(qs, "end", end_datebox.getTextBox()); @@ -961,7 +961,7 @@ public void got(final JSONValue json) { if (autoreload.getValue()) { history += "&autoreload=" + autoreoload_interval.getText(); } - if (!history.equals(History.getToken())) { + if (!history.equals(URL.decode(History.getToken()))) { History.newItem(history, false); } From fb414f702a4f2b28da5f94925037851c4ec9141e Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Thu, 29 Oct 2015 13:20:55 -0700 Subject: [PATCH 008/233] Bump the asyncbigtable version to take advantage of the rowfilter patch to properly enable regex matching in bigtable. Signed-off-by: Chris Larsen --- ...able-0.2.1-20151029.200718-1-jar-with-dependencies.jar.md5 | 1 + third_party/asyncbigtable/include.mk | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 third_party/asyncbigtable/asyncbigtable-0.2.1-20151029.200718-1-jar-with-dependencies.jar.md5 diff --git a/third_party/asyncbigtable/asyncbigtable-0.2.1-20151029.200718-1-jar-with-dependencies.jar.md5 b/third_party/asyncbigtable/asyncbigtable-0.2.1-20151029.200718-1-jar-with-dependencies.jar.md5 new file mode 100644 index 0000000000..75838d73bc --- /dev/null +++ b/third_party/asyncbigtable/asyncbigtable-0.2.1-20151029.200718-1-jar-with-dependencies.jar.md5 @@ -0,0 +1 @@ +d690357777c2c32e530429f72c54b2d4 \ No newline at end of file diff --git a/third_party/asyncbigtable/include.mk b/third_party/asyncbigtable/include.mk index e9eca08073..bfde52e600 100644 --- a/third_party/asyncbigtable/include.mk +++ b/third_party/asyncbigtable/include.mk @@ -13,9 +13,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see . -ASYNCBIGTABLE_VERSION := 0.2.0-20151027.224128-1 +ASYNCBIGTABLE_VERSION := 0.2.1-20151029.200718-1 ASYNCBIGTABLE := third_party/asyncbigtable/asyncbigtable-$(ASYNCBIGTABLE_VERSION)-jar-with-dependencies.jar -ASYNCBIGTABLE_BASE_URL := https://oss.sonatype.org/content/repositories/snapshots/com/pythian/opentsdb/asyncbigtable/0.2.0-SNAPSHOT/ +ASYNCBIGTABLE_BASE_URL := https://oss.sonatype.org/content/repositories/snapshots/com/pythian/opentsdb/asyncbigtable/0.2.1-SNAPSHOT/ $(ASYNCBIGTABLE): $(ASYNCBIGTABLE).md5 set dummy "$(ASYNCBIGTABLE_BASE_URL)" "$(ASYNCBIGTABLE)"; shift; $(FETCH_DEPENDENCY) From 8d4d282b38ff5eb703d4ddc1fc868c19de39ec4a Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Thu, 29 Oct 2015 14:33:36 -0700 Subject: [PATCH 009/233] Remove the HBASE_CONF requirement. All settings can be made in the opentsdb.conf file. Signed-off-by: Chris Larsen --- tsdb.in | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tsdb.in b/tsdb.in index 8eaf5d6c28..534deb03c1 100644 --- a/tsdb.in +++ b/tsdb.in @@ -112,11 +112,6 @@ then USE_BIGTABLE=1 echo "Running OpenTSDB with Bigtable support" - test -n "$HBASE_CONF" || { - echo >&2 'The environment variable HBASE_CONF must be set' - exit 1 - } - ALPN_BOOT_JAR=$(find $localdir -name alpn-boot\*.jar) exec $JAVA $JVMARGS -classpath "$CLASSPATH:$HBASE_CONF" -Xbootclasspath/p:$ALPN_BOOT_JAR net.opentsdb.tools.$MAINCLASS "$@" else From 5f5fe8c87ffa80b0e07fcb51df9836b0bfb0b63e Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Thu, 29 Oct 2015 14:56:59 -0700 Subject: [PATCH 010/233] Bump the AsyncBigtable client to fix commons-lang dependency. Signed-off-by: Chris Larsen --- ...gtable-0.2.0-20151027.224128-1-jar-with-dependencies.jar.md5 | 1 - ...gtable-0.2.1-20151029.200718-1-jar-with-dependencies.jar.md5 | 1 - ...gtable-0.2.1-20151029.214823-2-jar-with-dependencies.jar.md5 | 1 + third_party/asyncbigtable/include.mk | 2 +- 4 files changed, 2 insertions(+), 3 deletions(-) delete mode 100644 third_party/asyncbigtable/asyncbigtable-0.2.0-20151027.224128-1-jar-with-dependencies.jar.md5 delete mode 100644 third_party/asyncbigtable/asyncbigtable-0.2.1-20151029.200718-1-jar-with-dependencies.jar.md5 create mode 100644 third_party/asyncbigtable/asyncbigtable-0.2.1-20151029.214823-2-jar-with-dependencies.jar.md5 diff --git a/third_party/asyncbigtable/asyncbigtable-0.2.0-20151027.224128-1-jar-with-dependencies.jar.md5 b/third_party/asyncbigtable/asyncbigtable-0.2.0-20151027.224128-1-jar-with-dependencies.jar.md5 deleted file mode 100644 index ef206ef18b..0000000000 --- a/third_party/asyncbigtable/asyncbigtable-0.2.0-20151027.224128-1-jar-with-dependencies.jar.md5 +++ /dev/null @@ -1 +0,0 @@ -ef53c7422ee741867be4e925484f8d88 \ No newline at end of file diff --git a/third_party/asyncbigtable/asyncbigtable-0.2.1-20151029.200718-1-jar-with-dependencies.jar.md5 b/third_party/asyncbigtable/asyncbigtable-0.2.1-20151029.200718-1-jar-with-dependencies.jar.md5 deleted file mode 100644 index 75838d73bc..0000000000 --- a/third_party/asyncbigtable/asyncbigtable-0.2.1-20151029.200718-1-jar-with-dependencies.jar.md5 +++ /dev/null @@ -1 +0,0 @@ -d690357777c2c32e530429f72c54b2d4 \ No newline at end of file diff --git a/third_party/asyncbigtable/asyncbigtable-0.2.1-20151029.214823-2-jar-with-dependencies.jar.md5 b/third_party/asyncbigtable/asyncbigtable-0.2.1-20151029.214823-2-jar-with-dependencies.jar.md5 new file mode 100644 index 0000000000..dbfa539ba6 --- /dev/null +++ b/third_party/asyncbigtable/asyncbigtable-0.2.1-20151029.214823-2-jar-with-dependencies.jar.md5 @@ -0,0 +1 @@ +e07097fbc7023fd0ee108368a7ad7c73 \ No newline at end of file diff --git a/third_party/asyncbigtable/include.mk b/third_party/asyncbigtable/include.mk index bfde52e600..9e903a49f7 100644 --- a/third_party/asyncbigtable/include.mk +++ b/third_party/asyncbigtable/include.mk @@ -13,7 +13,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see . -ASYNCBIGTABLE_VERSION := 0.2.1-20151029.200718-1 +ASYNCBIGTABLE_VERSION := 0.2.1-20151029.214823-2 ASYNCBIGTABLE := third_party/asyncbigtable/asyncbigtable-$(ASYNCBIGTABLE_VERSION)-jar-with-dependencies.jar ASYNCBIGTABLE_BASE_URL := https://oss.sonatype.org/content/repositories/snapshots/com/pythian/opentsdb/asyncbigtable/0.2.1-SNAPSHOT/ From f6342f9a737fb2990e00d2e1fa77432098da988a Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Thu, 29 Oct 2015 17:26:24 -0700 Subject: [PATCH 011/233] Add a script to the ALPN include file to detect the Java version and download the proper version. And add MD5 files for each one. Signed-off-by: Chris Larsen --- .../alpn-boot-7.0.0.v20140317.jar.md5 | 1 + .../alpn-boot-7.1.0.v20141016.jar.md5 | 1 + .../alpn-boot-7.1.1.v20141016.jar.md5 | 1 + .../alpn-boot-7.1.2.v20141202.jar.md5 | 1 + .../alpn-boot-8.0.0.v20140317.jar.md5 | 1 + .../alpn-boot-8.1.0.v20141016.jar.md5 | 1 + .../alpn-boot-8.1.1.v20141016.jar.md5 | 1 + .../alpn-boot-8.1.2.v20141202.jar.md5 | 1 + .../alpn-boot-8.1.3.v20150130.jar.md5 | 1 + .../alpn-boot-8.1.4.v20150727.jar.md5 | 1 + .../alpn-boot-8.1.5.v20150921.jar.md5 | 1 + third_party/alpn-boot/include.mk | 43 ++++++++++++++++++- 12 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 third_party/alpn-boot/alpn-boot-7.0.0.v20140317.jar.md5 create mode 100644 third_party/alpn-boot/alpn-boot-7.1.0.v20141016.jar.md5 create mode 100644 third_party/alpn-boot/alpn-boot-7.1.1.v20141016.jar.md5 create mode 100644 third_party/alpn-boot/alpn-boot-7.1.2.v20141202.jar.md5 create mode 100644 third_party/alpn-boot/alpn-boot-8.0.0.v20140317.jar.md5 create mode 100644 third_party/alpn-boot/alpn-boot-8.1.0.v20141016.jar.md5 create mode 100644 third_party/alpn-boot/alpn-boot-8.1.1.v20141016.jar.md5 create mode 100644 third_party/alpn-boot/alpn-boot-8.1.2.v20141202.jar.md5 create mode 100644 third_party/alpn-boot/alpn-boot-8.1.3.v20150130.jar.md5 create mode 100644 third_party/alpn-boot/alpn-boot-8.1.4.v20150727.jar.md5 create mode 100644 third_party/alpn-boot/alpn-boot-8.1.5.v20150921.jar.md5 diff --git a/third_party/alpn-boot/alpn-boot-7.0.0.v20140317.jar.md5 b/third_party/alpn-boot/alpn-boot-7.0.0.v20140317.jar.md5 new file mode 100644 index 0000000000..6e005e9074 --- /dev/null +++ b/third_party/alpn-boot/alpn-boot-7.0.0.v20140317.jar.md5 @@ -0,0 +1 @@ +81e4f665ff2bf40720f9b345cee6b429 diff --git a/third_party/alpn-boot/alpn-boot-7.1.0.v20141016.jar.md5 b/third_party/alpn-boot/alpn-boot-7.1.0.v20141016.jar.md5 new file mode 100644 index 0000000000..529b4c8b5d --- /dev/null +++ b/third_party/alpn-boot/alpn-boot-7.1.0.v20141016.jar.md5 @@ -0,0 +1 @@ +b1569a1f34a0ca61d34c3c3e5020a8ef diff --git a/third_party/alpn-boot/alpn-boot-7.1.1.v20141016.jar.md5 b/third_party/alpn-boot/alpn-boot-7.1.1.v20141016.jar.md5 new file mode 100644 index 0000000000..d125a86172 --- /dev/null +++ b/third_party/alpn-boot/alpn-boot-7.1.1.v20141016.jar.md5 @@ -0,0 +1 @@ +d9add9c8eb6c087e408b076e6d823ddd diff --git a/third_party/alpn-boot/alpn-boot-7.1.2.v20141202.jar.md5 b/third_party/alpn-boot/alpn-boot-7.1.2.v20141202.jar.md5 new file mode 100644 index 0000000000..2ea2c00f10 --- /dev/null +++ b/third_party/alpn-boot/alpn-boot-7.1.2.v20141202.jar.md5 @@ -0,0 +1 @@ +391f659c583e2ea0f05515a6f6147620 diff --git a/third_party/alpn-boot/alpn-boot-8.0.0.v20140317.jar.md5 b/third_party/alpn-boot/alpn-boot-8.0.0.v20140317.jar.md5 new file mode 100644 index 0000000000..b34b6459eb --- /dev/null +++ b/third_party/alpn-boot/alpn-boot-8.0.0.v20140317.jar.md5 @@ -0,0 +1 @@ +de73395f7e20619699a07063e640d5f7 diff --git a/third_party/alpn-boot/alpn-boot-8.1.0.v20141016.jar.md5 b/third_party/alpn-boot/alpn-boot-8.1.0.v20141016.jar.md5 new file mode 100644 index 0000000000..89c8317c71 --- /dev/null +++ b/third_party/alpn-boot/alpn-boot-8.1.0.v20141016.jar.md5 @@ -0,0 +1 @@ +d4a325fdb7e86bd0d9ac583998165a84 diff --git a/third_party/alpn-boot/alpn-boot-8.1.1.v20141016.jar.md5 b/third_party/alpn-boot/alpn-boot-8.1.1.v20141016.jar.md5 new file mode 100644 index 0000000000..5993e27253 --- /dev/null +++ b/third_party/alpn-boot/alpn-boot-8.1.1.v20141016.jar.md5 @@ -0,0 +1 @@ +4655c087dda15743449ff31717d98e50 diff --git a/third_party/alpn-boot/alpn-boot-8.1.2.v20141202.jar.md5 b/third_party/alpn-boot/alpn-boot-8.1.2.v20141202.jar.md5 new file mode 100644 index 0000000000..01c9bb0cef --- /dev/null +++ b/third_party/alpn-boot/alpn-boot-8.1.2.v20141202.jar.md5 @@ -0,0 +1 @@ +9689564f4d7cc15918568f7006b85bf5 diff --git a/third_party/alpn-boot/alpn-boot-8.1.3.v20150130.jar.md5 b/third_party/alpn-boot/alpn-boot-8.1.3.v20150130.jar.md5 new file mode 100644 index 0000000000..a964d2acca --- /dev/null +++ b/third_party/alpn-boot/alpn-boot-8.1.3.v20150130.jar.md5 @@ -0,0 +1 @@ +a5803d4ff6ce36d15c750104a117dfb1 diff --git a/third_party/alpn-boot/alpn-boot-8.1.4.v20150727.jar.md5 b/third_party/alpn-boot/alpn-boot-8.1.4.v20150727.jar.md5 new file mode 100644 index 0000000000..c830ed3e9f --- /dev/null +++ b/third_party/alpn-boot/alpn-boot-8.1.4.v20150727.jar.md5 @@ -0,0 +1 @@ +1543b3403ae451ca2ec0944de403f6cc diff --git a/third_party/alpn-boot/alpn-boot-8.1.5.v20150921.jar.md5 b/third_party/alpn-boot/alpn-boot-8.1.5.v20150921.jar.md5 new file mode 100644 index 0000000000..38a5c04b33 --- /dev/null +++ b/third_party/alpn-boot/alpn-boot-8.1.5.v20150921.jar.md5 @@ -0,0 +1 @@ +b05ac69bd8697c4bfc4cf896dea63c94 diff --git a/third_party/alpn-boot/include.mk b/third_party/alpn-boot/include.mk index d7ead1358d..c4f94eb448 100644 --- a/third_party/alpn-boot/include.mk +++ b/third_party/alpn-boot/include.mk @@ -13,7 +13,48 @@ # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see . -ALPN_BOOT_VERSION := 7.1.3.v20150130 +# ALPN_BOOT_VERSION := 7.1.3.v20150130 +ALPN_BOOT_VERSION = $(shell version= ;\ + if [[ "@JAVA@" ]]; then \ + version=$$("@JAVA@" -version 2>&1 | awk -F '"' '/version/ {print $$2}'); \ + else\ + echo "Failed to parse Java version";\ + exit 1;\ + fi; \ + if [[ $$version =~ ^([0-9]+\.[0-9]+)\.([0-9])[_Uu]([0-9]+)$$ ]]; then \ + major=$${BASH_REMATCH[1]};\ + minor=$${BASH_REMATCH[2]}; \ + sub=$${BASH_REMATCH[3]}; \ + if [[ $$major = "1.7" ]]; then \ + if [[ $$sub < 71 ]]; then \ + echo "7.1.0.v20141016"; \ + elif [[ $$sub < 75 ]]; then \ + echo "7.1.2.v20141202"; \ + else \ + echo "7.1.3.v20150130"; \ + fi \ + elif [[ $$major = "1.8" ]]; then \ + if [[ $$sub < 25 ]]; then \ + echo "8.1.0.v20141016"; \ + elif [[ $$sub < 31 ]]; then \ + echo "8.1.2.v20141202"; \ + elif [[ $$sub < 51 ]]; then \ + echo "8.1.3.v20150130"; \ + elif [[ $$sub < 60 ]]; then \ + echo "8.1.4.v20150727"; \ + else \ + echo "8.1.5.v20150921"; \ + fi \ + else \ + echo "Unsupported major Java version: $$major"; \ + exit 1; \ + fi \ + else \ + echo "Possibly invalid Java version (couldn't parse): $$version"; \ + exit 1; \ + fi) + + ALPN_BOOT := third_party/alpn-boot/alpn-boot-$(ALPN_BOOT_VERSION).jar ALBPN_BOOT_BASE_URL := http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/$(ALPN_BOOT_VERSION) From ab13472281bb3a82ce3c503a3d157fc65298fd8b Mon Sep 17 00:00:00 2001 From: Arjun Satish Date: Mon, 6 Apr 2015 13:36:41 -0700 Subject: [PATCH 012/233] Write branch info for Version API Signed-off-by: Chris Larsen --- build-aux/gen_build_data.sh | 3 +++ src/tsd/RpcManager.java | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/build-aux/gen_build_data.sh b/build-aux/gen_build_data.sh index 927f2e63b1..4f211efbbc 100755 --- a/build-aux/gen_build_data.sh +++ b/build-aux/gen_build_data.sh @@ -39,6 +39,7 @@ eval "$sh" # Sets the timestamp and date variables. user=`whoami` host=`hostname` repo=`pwd` +branch=`git branch | grep -h '\*.*' | awk '{print $2}'` sh=`git rev-list --pretty=format:%h HEAD --max-count=1 \ | sed '1s/commit /full_rev=/;2s/^/short_rev=/'` @@ -92,6 +93,8 @@ public final class $CLASS { public static final String host = "$host"; /** Path to the repository in which this package was built. */ public static final String repo = "$repo"; + /** Git branch */ + public static final String branch = "$branch"; /** Human readable string describing the revision of this package. */ public static final String revisionString() { diff --git a/src/tsd/RpcManager.java b/src/tsd/RpcManager.java index 96b98d6f46..99ecd57eb0 100644 --- a/src/tsd/RpcManager.java +++ b/src/tsd/RpcManager.java @@ -623,7 +623,8 @@ public void execute(final TSDB tsdb, final HttpQuery query) throws version.put("user", BuildData.user); version.put("host", BuildData.host); version.put("repo", BuildData.repo); - + version.put("branch", BuildData.branch); + if (query.apiVersion() > 0) { query.sendReply(query.serializer().formatVersionV1(version)); } else { From ffb96291f0183b2af0344b6fb26aa17314e317a6 Mon Sep 17 00:00:00 2001 From: Yubao Liu Date: Thu, 29 Oct 2015 23:46:42 +0800 Subject: [PATCH 013/233] fix stuck metasync when salting is enabled MetaScanner extracts (salt_width + metric_width) bytes from tsuid to metric uid, this triggers IllegalArgumentException in UniqueId.getNameAsync(id), then MetaScanner.call() won't never call result.callback(null), and result.joinUninterruptibly() in MetaSync.run() never returns. BTW, I checked all usages of Const.SALT_WIDTH() and fixed some other similar wrong calculations. Signed-off-by: Chris Larsen --- src/core/BatchedDataPoints.java | 2 +- src/search/TimeSeriesLookup.java | 4 ++-- src/tools/MetaSync.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/BatchedDataPoints.java b/src/core/BatchedDataPoints.java index 3fcea6018c..c56143f234 100644 --- a/src/core/BatchedDataPoints.java +++ b/src/core/BatchedDataPoints.java @@ -304,7 +304,7 @@ public Deferred metricNameAsync() { if (row_key == null) { throw new IllegalStateException("Instance was not properly constructed!"); } - final byte[] id = Arrays.copyOfRange(row_key, 0, + final byte[] id = Arrays.copyOfRange(row_key, Const.SALT_WIDTH(), tsdb.metrics.width() + Const.SALT_WIDTH()); return tsdb.metrics.getNameAsync(id); } diff --git a/src/search/TimeSeriesLookup.java b/src/search/TimeSeriesLookup.java index 6c244b90f4..d640644c04 100644 --- a/src/search/TimeSeriesLookup.java +++ b/src/search/TimeSeriesLookup.java @@ -403,7 +403,7 @@ private Scanner getScanner(final int salt) { key = metric_uid; } else { key = new byte[Const.SALT_WIDTH() + TSDB.metrics_width()]; - key[0] = (byte)salt; + System.arraycopy(RowKey.getSaltBytes(salt), 0, key, 0, Const.SALT_WIDTH()); System.arraycopy(metric_uid, 0, key, Const.SALT_WIDTH(), metric_uid.length); } scanner.setStartKey(key); @@ -416,7 +416,7 @@ private Scanner getScanner(final int salt) { key = UniqueId.longToUID(uid, TSDB.metrics_width()); } else { key = new byte[Const.SALT_WIDTH() + TSDB.metrics_width()]; - key[0] = (byte)salt; + System.arraycopy(RowKey.getSaltBytes(salt), 0, key, 0, Const.SALT_WIDTH()); System.arraycopy(UniqueId.longToUID(uid, TSDB.metrics_width()), 0, key, Const.SALT_WIDTH(), metric_uid.length); } diff --git a/src/tools/MetaSync.java b/src/tools/MetaSync.java index 2303650a0e..b3c7510d23 100644 --- a/src/tools/MetaSync.java +++ b/src/tools/MetaSync.java @@ -392,7 +392,7 @@ public Object call(ArrayList> rows) // now process the UID metric meta data final byte[] metric_uid_bytes = - Arrays.copyOfRange(tsuid, 0, Const.SALT_WIDTH() + TSDB.metrics_width()); + Arrays.copyOfRange(tsuid, 0, TSDB.metrics_width()); final String metric_uid = UniqueId.uidToString(metric_uid_bytes); Long last_get = metric_uids.get(metric_uid); From ba636dc5de0adb2da9775d1afacf601f6b4002a9 Mon Sep 17 00:00:00 2001 From: Arjun Satish Date: Sun, 12 Jul 2015 18:55:42 -0700 Subject: [PATCH 014/233] Increase max number of rows to be returned per Scanner round trip via a config variable Signed-off-by: Chris Larsen --- src/query/QueryUtil.java | 1 + src/utils/Config.java | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/src/query/QueryUtil.java b/src/query/QueryUtil.java index ab168536ea..1fc1363632 100644 --- a/src/query/QueryUtil.java +++ b/src/query/QueryUtil.java @@ -202,6 +202,7 @@ public static Scanner getMetricScanner(final TSDB tsdb, final int salt_bucket, System.arraycopy(metric, 0, end_row, Const.SALT_WIDTH(), metric_width); final Scanner scanner = tsdb.getClient().newScanner(table); + scanner.setMaxNumRows(tsdb.getConfig().scanner_maxNumRows()); scanner.setStartKey(start_row); scanner.setStopKey(end_row); scanner.setFamily(family); diff --git a/src/utils/Config.java b/src/utils/Config.java index 3344ff2e3c..554891e0b1 100644 --- a/src/utils/Config.java +++ b/src/utils/Config.java @@ -102,6 +102,9 @@ public class Config { /** tsd.core.tree.enable_processing */ private boolean enable_tree_processing = false; + + /** tsd.storage.hbase.scanner.maxNumRows */ + private int scanner_max_num_rows = 128; /** * The list of properties configured to their defaults or modified by users @@ -219,6 +222,11 @@ public boolean enable_tsuid_incrementing() { public boolean enable_tsuid_tracking() { return enable_tsuid_tracking; } + + /** @return maximum number of rows to be fetched per round trip while scanning HBase */ + public int scanner_maxNumRows() { + return scanner_max_num_rows; + } /** @return whether or not chunked requests are supported */ public boolean enable_chunked_requests() { @@ -498,6 +506,7 @@ protected void setDefaults() { default_map.put("tsd.search.enable", "false"); default_map.put("tsd.search.plugin", ""); default_map.put("tsd.stats.canonical", "false"); + default_map.put("tsd.storage.hbase.scanner.maxNumRows", "128"); default_map.put("tsd.storage.fix_duplicates", "false"); default_map.put("tsd.storage.flush_interval", "1000"); default_map.put("tsd.storage.hbase.data_table", "tsdb"); @@ -635,6 +644,8 @@ protected void loadStaticVariables() { } enable_tree_processing = this.getBoolean("tsd.core.tree.enable_processing"); fix_duplicates = this.getBoolean("tsd.storage.fix_duplicates"); + scanner_max_num_rows = this.getInt("tsd.storage.hbase.scanner.maxNumRows"); + } /** From 602ee5676cf3c6cc2acdf6076f4fd96048ed9801 Mon Sep 17 00:00:00 2001 From: Arjun Satish Date: Sat, 31 Oct 2015 11:38:47 -0700 Subject: [PATCH 015/233] Add the Multiply aggregator Signed-off-by: Chris Larsen --- src/core/Aggregators.java | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/core/Aggregators.java b/src/core/Aggregators.java index 5edb598aa7..d70872e0de 100644 --- a/src/core/Aggregators.java +++ b/src/core/Aggregators.java @@ -53,6 +53,11 @@ public enum Interpolation { public static final Aggregator AVG = new Avg( Interpolation.LERP, "avg"); + /** Return the product of two time series + * @since 2.3 */ + public static final Aggregator MULTIPLY = new Multiply( + Interpolation.LERP, "multiply"); + /** Aggregator that returns the Standard Deviation of the data points. */ public static final Aggregator DEV = new StdDev( Interpolation.LERP, "dev"); @@ -139,6 +144,7 @@ public enum Interpolation { aggregators.put("min", MIN); aggregators.put("max", MAX); aggregators.put("avg", AVG); + aggregators.put("mult", MULTIPLY); aggregators.put("dev", DEV); aggregators.put("count", COUNT); aggregators.put("zimsum", ZIMSUM); @@ -313,6 +319,32 @@ public double runDouble(final Doubles values) { } + private static final class Multiply extends Aggregator { + + public Multiply(final Interpolation method, final String name) { + super(method, name); + } + + @Override + public long runLong(Longs values) { + long result = values.nextLongValue(); + while (values.hasNextValue()) { + result *= values.nextLongValue(); + } + return result; + } + + @Override + public double runDouble(Doubles values) { + double result = values.nextDoubleValue(); + while (values.hasNextValue()) { + result *= values.nextDoubleValue(); + } + return result; + } + + } + /** * Standard Deviation aggregator. * Can compute without storing all of the data points in memory at the same From d21d43a9e4e7f02fd1afa1d3f00c51c4bbbeb885 Mon Sep 17 00:00:00 2001 From: Arjun Satish Date: Sat, 31 Oct 2015 12:11:55 -0700 Subject: [PATCH 016/233] Make the AggregationIterator class and ctor public. Signed-off-by: Chris Larsen --- src/core/AggregationIterator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/AggregationIterator.java b/src/core/AggregationIterator.java index 28d77b9939..a53636b751 100644 --- a/src/core/AggregationIterator.java +++ b/src/core/AggregationIterator.java @@ -115,7 +115,7 @@ * to a special, really large value (too large to be a valid timestamp). *

*/ -final class AggregationIterator implements SeekableView, DataPoint, +public class AggregationIterator implements SeekableView, DataPoint, Aggregator.Longs, Aggregator.Doubles { private static final Logger LOG = @@ -290,7 +290,7 @@ public static AggregationIterator create(final List spans, * @param rate If {@code true}, the rate of the series will be used instead * of the actual values. */ - private AggregationIterator(final SeekableView[] iterators, + public AggregationIterator(final SeekableView[] iterators, final long start_time, final long end_time, final Aggregator aggregator, From c34e99496edff9622c1474167f9ee63fddfea251 Mon Sep 17 00:00:00 2001 From: Arjun Satish Date: Sat, 31 Oct 2015 12:19:23 -0700 Subject: [PATCH 017/233] Add the PostAggregatedDataPoints class to store an array of processed data points along with the original results containing meta data about the time series. Signed-off-by: Chris Larsen --- .../expression/PostAggregatedDataPoints.java | 176 ++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 src/query/expression/PostAggregatedDataPoints.java diff --git a/src/query/expression/PostAggregatedDataPoints.java b/src/query/expression/PostAggregatedDataPoints.java new file mode 100644 index 0000000000..f8323516be --- /dev/null +++ b/src/query/expression/PostAggregatedDataPoints.java @@ -0,0 +1,176 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + +import org.hbase.async.Bytes.ByteMap; + +import net.opentsdb.core.DataPoint; +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.SeekableView; +import net.opentsdb.meta.Annotation; + +import com.google.common.collect.Maps; +import com.stumbleupon.async.Deferred; + +public class PostAggregatedDataPoints implements DataPoints { + + private final DataPoints baseDataPoints; + private final DataPoint[] points; + + private String alias = null; + + public PostAggregatedDataPoints(DataPoints baseDataPoints, DataPoint[] points) { + this.baseDataPoints = baseDataPoints; + this.points = points; + } + + @Override + public String metricName() { + if (alias != null) return alias; + else return baseDataPoints.metricName(); + } + + @Override + public Deferred metricNameAsync() { + if (alias != null) return Deferred.fromResult(alias); + return baseDataPoints.metricNameAsync(); + } + + @Override + public Map getTags() { + if (alias != null) return Maps.newHashMap(); + else return baseDataPoints.getTags(); + } + + @Override + public Deferred> getTagsAsync() { + Map def = new HashMap(); + if (alias != null) return Deferred.fromResult(def); + return baseDataPoints.getTagsAsync(); + } + + @Override + public List getAggregatedTags() { + return baseDataPoints.getAggregatedTags(); + } + + public void setAlias(String alias) { + this.alias = alias; + } + + @Override + public Deferred> getAggregatedTagsAsync() { + return baseDataPoints.getAggregatedTagsAsync(); + } + + @Override + public List getTSUIDs() { + return baseDataPoints.getTSUIDs(); + } + + @Override + public List getAnnotations() { + return baseDataPoints.getAnnotations(); + } + + @Override + public int size() { + return points.length; + } + + @Override + public int aggregatedSize() { + return points.length; + } + + @Override + public SeekableView iterator() { + return new SeekableViewImpl(points); + } + + @Override + public long timestamp(int i) { + return points[i].timestamp(); + } + + @Override + public boolean isInteger(int i) { + return points[i].isInteger(); + } + + @Override + public long longValue(int i) { + return points[i].longValue(); + } + + @Override + public double doubleValue(int i) { + return points[i].doubleValue(); + } + + static class SeekableViewImpl implements SeekableView { + + private int pos=0; + private final DataPoint[] dps; + + public SeekableViewImpl(DataPoint[] dps) { + this.dps = dps; + } + + @Override + public boolean hasNext() { + return pos < dps.length; + } + + @Override + public DataPoint next() { + if (hasNext()) { + return dps[pos++]; + } else { + throw new NoSuchElementException("tsdb uses exceptions to determine end of iterators"); + } + } + + @Override + public void remove() { + throw new RuntimeException("Not supported exception"); + } + + @Override + public void seek(long timestamp) { + for (int i=pos; i= timestamp) { + break; + } else { + pos++; + } + } + } + } + + @Override + public ByteMap getTagUids() { + return baseDataPoints.getTagUids(); + } + + @Override + public int getQueryIndex() { + return baseDataPoints.getQueryIndex(); + } + +} From 223b624d5bd20f03c5e5566e07cf4698e00b2420 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sat, 31 Oct 2015 12:28:07 -0700 Subject: [PATCH 018/233] Cleanup, add comments and unit tests to PostAggregatedDataPoints. Also return an empty list for aggregated tags similar to the tags. We may want to revisit that at some point as it could still be useful to have the tags and the agg tags and just use the alias for metrics. Signed-off-by: Chris Larsen --- Makefile.am | 2 + .../expression/PostAggregatedDataPoints.java | 116 ++++++--- .../TestPostAggregatedDataPoints.java | 220 ++++++++++++++++++ 3 files changed, 300 insertions(+), 38 deletions(-) create mode 100644 test/query/expression/TestPostAggregatedDataPoints.java diff --git a/Makefile.am b/Makefile.am index 56c92b14b4..0aae69b4c9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -74,6 +74,7 @@ tsdb_SRC := \ src/meta/TSUIDQuery.java \ src/meta/UIDMeta.java \ src/query/QueryUtil.java \ + src/query/expression/PostAggregatedDataPoints.java \ src/query/filter/TagVFilter.java \ src/query/filter/TagVLiteralOrFilter.java \ src/query/filter/TagVNotKeyFilter.java \ @@ -218,6 +219,7 @@ test_SRC := \ test/meta/TestTSMeta.java \ test/meta/TestTSUIDQuery.java \ test/meta/TestUIDMeta.java \ + test/query/expression/TestPostAggregatedDataPoints.java \ test/query/filter/TestTagVFilter.java \ test/query/filter/TestTagVLiteralOrFilter.java \ test/query/filter/TestTagVNotKeyFilter.java \ diff --git a/src/query/expression/PostAggregatedDataPoints.java b/src/query/expression/PostAggregatedDataPoints.java index f8323516be..872def9c49 100644 --- a/src/query/expression/PostAggregatedDataPoints.java +++ b/src/query/expression/PostAggregatedDataPoints.java @@ -12,7 +12,7 @@ // see . package net.opentsdb.query.expression; -import java.util.HashMap; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; @@ -24,68 +24,109 @@ import net.opentsdb.core.SeekableView; import net.opentsdb.meta.Annotation; -import com.google.common.collect.Maps; import com.stumbleupon.async.Deferred; +/** + * A class to store an array of data points processed through expressions along + * with the original meta data of the result set (metric, tags, etc). + * @since 2.3 + */ public class PostAggregatedDataPoints implements DataPoints { - private final DataPoints baseDataPoints; + /** The original results from storage, used for fetching meta data */ + private final DataPoints base_data_points; + + /** The results of the expression calculation */ private final DataPoint[] points; + /** An optional alias for the results */ private String alias = null; - public PostAggregatedDataPoints(DataPoints baseDataPoints, DataPoint[] points) { - this.baseDataPoints = baseDataPoints; + /** + * Default ctor + * @param base_data_points The original results from storage for fetching meta + * @param points The results of the expression calculation + */ + public PostAggregatedDataPoints(final DataPoints base_data_points, + final DataPoint[] points) { + if (base_data_points == null) { + throw new IllegalArgumentException("base_data_points cannot be null"); + } + if (points == null) { + throw new IllegalArgumentException("points cannot be null"); + } + this.base_data_points = base_data_points; this.points = points; } @Override public String metricName() { - if (alias != null) return alias; - else return baseDataPoints.metricName(); + if (alias != null) { + return alias; + } else { + return base_data_points.metricName(); + } } @Override public Deferred metricNameAsync() { - if (alias != null) return Deferred.fromResult(alias); - return baseDataPoints.metricNameAsync(); + if (alias != null) { + return Deferred.fromResult(alias); + } + return base_data_points.metricNameAsync(); } @Override public Map getTags() { - if (alias != null) return Maps.newHashMap(); - else return baseDataPoints.getTags(); + if (alias != null) { + return Collections.emptyMap(); + } else { + return base_data_points.getTags(); + } } @Override public Deferred> getTagsAsync() { - Map def = new HashMap(); - if (alias != null) return Deferred.fromResult(def); - return baseDataPoints.getTagsAsync(); + if (alias != null) { + return Deferred.fromResult(Collections.emptyMap()); + } + return base_data_points.getTagsAsync(); } @Override public List getAggregatedTags() { - return baseDataPoints.getAggregatedTags(); - } - - public void setAlias(String alias) { - this.alias = alias; + if (alias != null) { + return Collections.emptyList(); + } + return base_data_points.getAggregatedTags(); } @Override public Deferred> getAggregatedTagsAsync() { - return baseDataPoints.getAggregatedTagsAsync(); + if (alias != null) { + return Deferred.fromResult(Collections.emptyList()); + } + return base_data_points.getAggregatedTagsAsync(); } @Override public List getTSUIDs() { - return baseDataPoints.getTSUIDs(); + return base_data_points.getTSUIDs(); } @Override public List getAnnotations() { - return baseDataPoints.getAnnotations(); + return base_data_points.getAnnotations(); + } + + @Override + public ByteMap getTagUids() { + return base_data_points.getTagUids(); + } + + @Override + public int getQueryIndex() { + return base_data_points.getQueryIndex(); } @Override @@ -122,13 +163,17 @@ public long longValue(int i) { public double doubleValue(int i) { return points[i].doubleValue(); } - + + /** + * An iterator working over the data points resulting from the expression + * calculation. + */ static class SeekableViewImpl implements SeekableView { - private int pos=0; + private int pos = 0; private final DataPoint[] dps; - - public SeekableViewImpl(DataPoint[] dps) { + + SeekableViewImpl(final DataPoint[] dps) { this.dps = dps; } @@ -142,18 +187,18 @@ public DataPoint next() { if (hasNext()) { return dps[pos++]; } else { - throw new NoSuchElementException("tsdb uses exceptions to determine end of iterators"); + throw new NoSuchElementException("no more elements"); } } @Override public void remove() { - throw new RuntimeException("Not supported exception"); + throw new UnsupportedOperationException(); } @Override public void seek(long timestamp) { - for (int i=pos; i= timestamp) { break; } else { @@ -163,14 +208,9 @@ public void seek(long timestamp) { } } - @Override - public ByteMap getTagUids() { - return baseDataPoints.getTagUids(); - } - - @Override - public int getQueryIndex() { - return baseDataPoints.getQueryIndex(); + /** @param alias The alias to set for the time series. Used in place of + * the metric and nulls out all tags. */ + public void setAlias(String alias) { + this.alias = alias; } - } diff --git a/test/query/expression/TestPostAggregatedDataPoints.java b/test/query/expression/TestPostAggregatedDataPoints.java new file mode 100644 index 0000000000..aa940d9072 --- /dev/null +++ b/test/query/expression/TestPostAggregatedDataPoints.java @@ -0,0 +1,220 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + +import net.opentsdb.core.DataPoint; +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.MutableDataPoint; +import net.opentsdb.core.SeekableView; +import net.opentsdb.meta.Annotation; + +import org.hbase.async.Bytes.ByteMap; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import com.stumbleupon.async.Deferred; + +@RunWith(PowerMockRunner.class) +@PowerMockIgnore({"javax.management.*", "javax.xml.*", + "ch.qos.*", "org.slf4j.*", + "com.sum.*", "org.xml.*"}) +@PrepareForTest({ Annotation.class }) +public class TestPostAggregatedDataPoints { + private static int NUM_POINTS = 5; + private static String METRIC_NAME = "sys.cpu"; + private static long BASE_TIME = 1356998400000L; + private static int TIME_INTERVAL = 60000; + + private DataPoints base_data_points; + private DataPoint[] points; + private Map tags; + private List agg_tags; + private List tsuids; + private List annotations; + private ByteMap tag_uids; + + @Before + public void before() throws Exception { + base_data_points = PowerMockito.mock(DataPoints.class); + points = new MutableDataPoint[NUM_POINTS]; + + long ts = BASE_TIME; + for (int i = 0; i < NUM_POINTS; i++) { + MutableDataPoint mdp = new MutableDataPoint(); + mdp.reset(ts, i); + points[i] = mdp; + ts += TIME_INTERVAL; + } + + tags = new HashMap(1); + tags.put("colo", "lga"); + agg_tags = new ArrayList(1); + agg_tags.add("host"); + tsuids = new ArrayList(1); + tsuids.add("0101010202"); // just 1 byte UIDs for kicks + annotations = new ArrayList(1); + annotations.add(PowerMockito.mock(Annotation.class)); + tag_uids = new ByteMap(); + tag_uids.put(new byte[] { 1 }, new byte[] { 1 }); + + when(base_data_points.metricName()).thenReturn(METRIC_NAME); + when(base_data_points.metricNameAsync()).thenReturn( + Deferred.fromResult(METRIC_NAME)); + when(base_data_points.getTags()).thenReturn(tags); + when(base_data_points.getTagsAsync()).thenReturn(Deferred.fromResult(tags)); + when(base_data_points.getAggregatedTags()).thenReturn(agg_tags); + when(base_data_points.getAggregatedTagsAsync()).thenReturn( + Deferred.fromResult(agg_tags)); + when(base_data_points.getTSUIDs()).thenReturn(tsuids); + when(base_data_points.getAnnotations()).thenReturn(annotations); + when(base_data_points.getTagUids()).thenReturn(tag_uids); + when(base_data_points.getQueryIndex()).thenReturn(42); + } + + @Test + public void ctorDefaults() throws Exception { + final PostAggregatedDataPoints dps = new PostAggregatedDataPoints( + base_data_points, points); + assertEquals(METRIC_NAME, dps.metricName()); + assertEquals(METRIC_NAME, dps.metricNameAsync().join()); + assertSame(tags, dps.getTags()); + assertSame(tags, dps.getTagsAsync().join()); + assertSame(agg_tags, dps.getAggregatedTags()); + assertSame(agg_tags, dps.getAggregatedTagsAsync().join()); + assertSame(tsuids, dps.getTSUIDs()); + assertSame(annotations, dps.getAnnotations()); + assertSame(tag_uids, dps.getTagUids()); + assertEquals(42, dps.getQueryIndex()); + assertEquals(5, dps.size()); + assertEquals(5, dps.aggregatedSize()); + + // values + final SeekableView iterator = dps.iterator(); + int values = 0; + long value = 0; + long ts = BASE_TIME; + while(iterator.hasNext()) { + final DataPoint dp = iterator.next(); + assertEquals(value++, dp.longValue()); + assertEquals(ts, dp.timestamp()); + ts += TIME_INTERVAL; + values++; + } + assertEquals(5, values); + assertFalse(iterator.hasNext()); + try { + iterator.next(); + fail("Expected a NoSuchElementException"); + } catch (NoSuchElementException e) { } + + assertEquals(BASE_TIME + (4 * TIME_INTERVAL), dps.timestamp(4)); + assertEquals(4, dps.longValue(4)); + assertTrue(dps.isInteger(4)); + try { + assertEquals(4, dps.doubleValue(4), 0.00); + fail("Expected a ClassCastException"); + } catch (ClassCastException e) { } + + try { + dps.timestamp(5); + fail("Expected a ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException e) { } + try { + dps.isInteger(5); + fail("Expected a ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException e) { } + try { + dps.longValue(5); + fail("Expected a ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException e) { } + try { + dps.doubleValue(5); + fail("Expected a ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException e) { } + } + + @Test (expected = IllegalArgumentException.class) + public void ctorNullBase() throws Exception { + new PostAggregatedDataPoints(null, points); + } + + @Test (expected = IllegalArgumentException.class) + public void ctorNullPoints() throws Exception { + new PostAggregatedDataPoints(base_data_points, null); + } + + @Test + public void alias() throws Exception { + final PostAggregatedDataPoints dps = new PostAggregatedDataPoints( + base_data_points, points); + final String alias = "ein"; + dps.setAlias(alias); + assertEquals(alias, dps.metricName()); + assertEquals(alias, dps.metricNameAsync().join()); + assertTrue(dps.getTags().isEmpty()); + assertTrue(dps.getTagsAsync().join().isEmpty()); + assertTrue(dps.getAggregatedTags().isEmpty()); + assertTrue(dps.getAggregatedTagsAsync().join().isEmpty()); + assertSame(tsuids, dps.getTSUIDs()); + assertSame(annotations, dps.getAnnotations()); + assertSame(tag_uids, dps.getTagUids()); + assertEquals(42, dps.getQueryIndex()); + assertEquals(5, dps.size()); + assertEquals(5, dps.aggregatedSize()); + } + + @Test + public void emptyPoints() throws Exception { + points = new MutableDataPoint[0]; + final PostAggregatedDataPoints dps = new PostAggregatedDataPoints( + base_data_points, points); + assertEquals(METRIC_NAME, dps.metricName()); + assertEquals(METRIC_NAME, dps.metricNameAsync().join()); + assertSame(tags, dps.getTags()); + assertSame(tags, dps.getTagsAsync().join()); + assertSame(agg_tags, dps.getAggregatedTags()); + assertSame(agg_tags, dps.getAggregatedTagsAsync().join()); + assertSame(tsuids, dps.getTSUIDs()); + assertSame(annotations, dps.getAnnotations()); + assertSame(tag_uids, dps.getTagUids()); + assertEquals(42, dps.getQueryIndex()); + assertEquals(0, dps.size()); + assertEquals(0, dps.aggregatedSize()); + + // values + final SeekableView iterator = dps.iterator(); + assertFalse(iterator.hasNext()); + try { + iterator.next(); + fail("Expected a NoSuchElementException"); + } catch (NoSuchElementException e) { } + } +} From 5e229f00c08ee1647cf1ddd8793d1d761e0aec07 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sat, 31 Oct 2015 16:09:22 -0700 Subject: [PATCH 019/233] Rework the SeekableViewsForTest generator to allow for configurable starting values and increment values. Signed-off-by: Chris Larsen --- test/core/SeekableViewsForTest.java | 132 ++++++++++++++++------------ 1 file changed, 74 insertions(+), 58 deletions(-) diff --git a/test/core/SeekableViewsForTest.java b/test/core/SeekableViewsForTest.java index dab66d7c37..e53dc27b15 100644 --- a/test/core/SeekableViewsForTest.java +++ b/test/core/SeekableViewsForTest.java @@ -32,7 +32,8 @@ public static SeekableView fromArray(final DataPoint[] data_points) { } /** - * Creates a {@link SeekableView} that generates a sequence of data points. + * Creates a {@link SeekableView} that generates a sequence of data points + * where the starting value is 1 and it is incremented by 1 each iteration. * @param start_time Starting timestamp * @param sample_period Average sample period of data points * @param num_data_points Total number of data points to generate @@ -43,8 +44,28 @@ public static SeekableView generator(final long start_time, final long sample_period, final int num_data_points, final boolean is_integer) { + return generator(start_time, sample_period, num_data_points, + is_integer, 0, 1); + } + + /** + * Creates a {@link SeekableView} that generates a sequence of data points. + * @param start_time Starting timestamp + * @param sample_period Average sample period of data points + * @param num_data_points Total number of data points to generate + * @param is_integer True to generate a sequence of integer data points. + * @param starting_value The starting data point value. + * @param increment How much to increment the values each iteration. + * @return A {@link SeekableView} object + */ + public static SeekableView generator(final long start_time, + final long sample_period, + final int num_data_points, + final boolean is_integer, + final double starting_value, + final double increment) { return new DataPointGenerator(start_time, sample_period, num_data_points, - is_integer); + is_integer, starting_value, increment); } /** Iterates an array of data points. */ @@ -88,32 +109,38 @@ public void seek(long timestamp) { /** Generates a sequence of data points. */ private static class DataPointGenerator implements SeekableView { - private final long start_time_ms; private final long sample_period_ms; private final int num_data_points; private final boolean is_integer; + private final double increment; private final MutableDataPoint current_data = new MutableDataPoint(); - private int current = 0; - + private final MutableDataPoint next_data = new MutableDataPoint(); + private int dps_emitted = 0; + DataPointGenerator(final long start_time_ms, final long sample_period_ms, - final int num_data_points, final boolean is_integer) { - this.start_time_ms = start_time_ms; + final int num_data_points, final boolean is_integer, + final double starting_value, final double increment) { this.sample_period_ms = sample_period_ms; this.num_data_points = num_data_points; this.is_integer = is_integer; - rewind(); + this.increment = increment; + if (is_integer) { + next_data.reset(start_time_ms, (long)starting_value); + } else { + next_data.reset(start_time_ms, starting_value); + } } @Override public boolean hasNext() { - return current < num_data_points; + return dps_emitted < num_data_points; } @Override public DataPoint next() { if (hasNext()) { - generateData(); - ++current; + current_data.reset(next_data); + advance(); return current_data; } throw new NoSuchElementException("no more values"); @@ -126,44 +153,32 @@ public void remove() { @Override public void seek(long timestamp) { - rewind(); - current = (int)((timestamp -1 - start_time_ms) / sample_period_ms); - if (current < 0) { - current = 0; - } - while (generateTimestamp() < timestamp) { - ++current; + while (next_data.timestamp() < timestamp && dps_emitted < num_data_points) { + advance(); } } - - private void rewind() { - current = 0; - generateData(); - } - - private void generateData() { + + private void advance() { if (is_integer) { - current_data.reset(generateTimestamp(), current); + next_data.reset(next_data.timestamp() + sample_period_ms, + next_data.longValue() + (long)increment); } else { - current_data.reset(generateTimestamp(), (double)current); + next_data.reset(next_data.timestamp() + sample_period_ms, + next_data.doubleValue() + increment); } - } - - private long generateTimestamp() { - long timestamp = start_time_ms + sample_period_ms * current; - return timestamp + (((current % 2) == 0) ? -1000 : 1000); + dps_emitted++; } } @Test public void testDataPointGenerator() { - DataPointGenerator dpg = new DataPointGenerator(100000, 10000, 5, true); + SeekableView dpg = generator(100000, 10000, 5, true); DataPoint[] expected_data_points = new DataPoint[] { - MutableDataPoint.ofLongValue(99000, 0), - MutableDataPoint.ofLongValue(111000, 1), - MutableDataPoint.ofLongValue(119000, 2), - MutableDataPoint.ofLongValue(131000, 3), - MutableDataPoint.ofLongValue(139000, 4), + MutableDataPoint.ofLongValue(100000, 0), + MutableDataPoint.ofLongValue(110000, 1), + MutableDataPoint.ofLongValue(120000, 2), + MutableDataPoint.ofLongValue(130000, 3), + MutableDataPoint.ofLongValue(140000, 4), }; for (DataPoint expected: expected_data_points) { assertTrue(dpg.hasNext()); @@ -176,13 +191,13 @@ public void testDataPointGenerator() { @Test public void testDataPointGenerator_double() { - DataPointGenerator dpg = new DataPointGenerator(100000, 10000, 5, false); + SeekableView dpg = generator(100000, 10000, 5, false); DataPoint[] expected_data_points = new DataPoint[] { - MutableDataPoint.ofDoubleValue(99000, 0), - MutableDataPoint.ofDoubleValue(111000, 1), - MutableDataPoint.ofDoubleValue(119000, 2), - MutableDataPoint.ofDoubleValue(131000, 3), - MutableDataPoint.ofDoubleValue(139000, 4), + MutableDataPoint.ofDoubleValue(100000, 0), + MutableDataPoint.ofDoubleValue(110000, 1), + MutableDataPoint.ofDoubleValue(120000, 2), + MutableDataPoint.ofDoubleValue(130000, 3), + MutableDataPoint.ofDoubleValue(140000, 4), }; for (DataPoint expected: expected_data_points) { assertTrue(dpg.hasNext()); @@ -195,12 +210,12 @@ public void testDataPointGenerator_double() { @Test public void testDataPointGenerator_seek() { - DataPointGenerator dpg = new DataPointGenerator(100000, 10000, 5, true); + SeekableView dpg = generator(100000, 10000, 5, true); dpg.seek(119000); DataPoint[] expected_data_points = new DataPoint[] { - MutableDataPoint.ofLongValue(119000, 2), - MutableDataPoint.ofLongValue(131000, 3), - MutableDataPoint.ofLongValue(139000, 4), + MutableDataPoint.ofLongValue(120000, 2), + MutableDataPoint.ofLongValue(130000, 3), + MutableDataPoint.ofLongValue(140000, 4), }; for (DataPoint expected: expected_data_points) { assertTrue(dpg.hasNext()); @@ -213,13 +228,14 @@ public void testDataPointGenerator_seek() { @Test public void testDataPointGenerator_seekToFirst() { - DataPointGenerator dpg = new DataPointGenerator(100000, 10000, 5, true); + SeekableView dpg = generator(100000, 10000, 5, true); dpg.seek(100000); DataPoint[] expected_data_points = new DataPoint[] { - MutableDataPoint.ofLongValue(111000, 1), - MutableDataPoint.ofLongValue(119000, 2), - MutableDataPoint.ofLongValue(131000, 3), - MutableDataPoint.ofLongValue(139000, 4), + MutableDataPoint.ofLongValue(100000, 0), + MutableDataPoint.ofLongValue(110000, 1), + MutableDataPoint.ofLongValue(120000, 2), + MutableDataPoint.ofLongValue(130000, 3), + MutableDataPoint.ofLongValue(140000, 4), }; for (DataPoint expected: expected_data_points) { assertTrue(dpg.hasNext()); @@ -232,13 +248,13 @@ public void testDataPointGenerator_seekToFirst() { @Test public void testDataPointGenerator_seekToSecond() { - DataPointGenerator dpg = new DataPointGenerator(100000, 10000, 5, true); + SeekableView dpg = generator(100000, 10000, 5, true); dpg.seek(100001); DataPoint[] expected_data_points = new DataPoint[] { - MutableDataPoint.ofLongValue(111000, 1), - MutableDataPoint.ofLongValue(119000, 2), - MutableDataPoint.ofLongValue(131000, 3), - MutableDataPoint.ofLongValue(139000, 4), + MutableDataPoint.ofLongValue(110000, 1), + MutableDataPoint.ofLongValue(120000, 2), + MutableDataPoint.ofLongValue(130000, 3), + MutableDataPoint.ofLongValue(140000, 4), }; for (DataPoint expected: expected_data_points) { assertTrue(dpg.hasNext()); From 02fd5ae865022e5899dde8a10fd88977884e526d Mon Sep 17 00:00:00 2001 From: Arjun Satish Date: Sat, 31 Oct 2015 16:24:58 -0700 Subject: [PATCH 020/233] Add a MovingAverage expression implementation for the Graphite endpoint. Signed-off-by: Chris Larsen --- src/query/expression/MovingAverage.java | 235 ++++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 src/query/expression/MovingAverage.java diff --git a/src/query/expression/MovingAverage.java b/src/query/expression/MovingAverage.java new file mode 100644 index 0000000000..d9bda06a26 --- /dev/null +++ b/src/query/expression/MovingAverage.java @@ -0,0 +1,235 @@ +package net.opentsdb.query.expression; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import net.opentsdb.core.AggregationIterator; +import net.opentsdb.core.Aggregator; +import net.opentsdb.core.Aggregators; +import net.opentsdb.core.DataPoint; +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.MutableDataPoint; +import net.opentsdb.core.SeekableView; +import net.opentsdb.core.TSQuery; +import net.opentsdb.core.Aggregators.Interpolation; + +import com.google.common.collect.Lists; + +public class MovingAverage implements Expression { + + @Override + public DataPoints[] evaluate(final TSQuery data_query, List queryResults, List params) { + if (queryResults == null || queryResults.isEmpty()) { + return new DataPoints[]{}; + } + + if (params == null || params.isEmpty()) { + throw new NullPointerException("Need aggregation window for moving average"); + } + + String param = params.get(0); + if (param == null || param.length() == 0) { + throw new NullPointerException("Invalid window='" + param + "'"); + } + + param = param.trim(); + + long numPoints = -1; + boolean isTimeUnit = false; + if (param.matches("[0-9]+")) { + numPoints = Integer.parseInt(param); + } else if (param.startsWith("'") && param.endsWith("'")) { + numPoints = parseParam(param); + isTimeUnit = true; + } + + if (numPoints <= 0) { + throw new RuntimeException("numPoints <= 0"); + } + + int size = 0; + for (DataPoints[] results: queryResults) { + size = size + results.length; + } + + PostAggregatedDataPoints[] seekablePoints = new PostAggregatedDataPoints[size]; + int ix=0; + // one or more queries (m=...&m=...&m=...) + for (DataPoints[] results: queryResults) { + // group bys (m=sum:foo{host=*}) + for (DataPoints dpoints: results) { + List mutablePoints = new ArrayList(); + for (DataPoint point: dpoints) { + mutablePoints.add(point.isInteger() ? + MutableDataPoint.ofLongValue(point.timestamp(), point.longValue()) + : MutableDataPoint.ofDoubleValue(point.timestamp(), point.doubleValue())); + } + + seekablePoints[ix++] = new PostAggregatedDataPoints(dpoints, + mutablePoints.toArray(new DataPoint[mutablePoints.size()])); + } + } + + SeekableView[] views = new SeekableView[size]; + for (int i=0; i points = Lists.newArrayList(); + while (view.hasNext()) { + DataPoint mdp = view.next(); + points.add(mdp.isInteger() ? + MutableDataPoint.ofLongValue(mdp.timestamp(), mdp.longValue()) : + MutableDataPoint.ofDoubleValue(mdp.timestamp(), mdp.doubleValue())); + } + + if (queryResults.size() > 0 && queryResults.get(0).length > 0) { + return new DataPoints[]{new PostAggregatedDataPoints(queryResults.get(0)[0], + points.toArray(new DataPoint[points.size()]))}; + } else { + return new DataPoints[]{}; + } + } + + public long parseParam(String param) { + char[] chars = param.toCharArray(); + int tuIndex = 0; + for (int c = 1; c < chars.length; c++) { + if (Character.isDigit(chars[c])) { + tuIndex++; + } else { + break; + } + } + + if (tuIndex == 0) { + throw new RuntimeException("Invalid Parameter: " + param); + } + + int time = Integer.parseInt(param.substring(1, tuIndex + 1)); + String unit = param.substring(tuIndex+1, param.length() - 1); + + if ("min".equals(unit)) { + return TimeUnit.MILLISECONDS.convert(time, TimeUnit.MINUTES); + } else if ("hr".equals(unit)) { + return TimeUnit.MILLISECONDS.convert(time, TimeUnit.HOURS); + } else if ("sec".equals(unit)) { + return TimeUnit.MILLISECONDS.convert(time, TimeUnit.SECONDS); + } else { + throw new RuntimeException("unknown time unit=" + unit); + } + + } + + @Override + public String writeStringField(List queryParams, String innerExpression) { + return "movingAverage(" + innerExpression + ")"; + } + + static final class MovingAverageAggregator extends Aggregator { + private LinkedList list = new LinkedList(); + private final long numPoints; + private final boolean isTimeUnit; + + public MovingAverageAggregator(final Interpolation method, final String name, long numPoints, boolean isTimeUnit) { + super(method, name); + this.numPoints = numPoints; + this.isTimeUnit = isTimeUnit; + } + + @Override + public long runLong(final Longs values) { + long sum = values.nextLongValue(); + while (values.hasNextValue()) { + sum += values.nextLongValue(); + } + + if (values instanceof DataPoint) { + long ts = ((DataPoint) values).timestamp(); + list.addFirst(new SumPoint(ts, sum)); + } + + long result=0; int count=0; + + Iterator iter = list.iterator(); + SumPoint first = iter.next(); + boolean conditionMet = false; + + // now sum up the preceeding points + while(iter.hasNext()) { + SumPoint next = iter.next(); + result += (Long) next.val; + count++; + if (!isTimeUnit && count >= numPoints) { + conditionMet = true; + break; + } else if (isTimeUnit && ((first.ts - next.ts) > numPoints)) { + conditionMet = true; + break; + } + } + + if (!conditionMet || count == 0) { + return 0; + } + + return result/count; + } + + @Override + public double runDouble(Doubles values) { + double sum = values.nextDoubleValue(); + while (values.hasNextValue()) { + sum += values.nextDoubleValue(); + } + + if (values instanceof DataPoint) { + long ts = ((DataPoint) values).timestamp(); + list.addFirst(new SumPoint(ts, sum)); + } + + double result=0; int count=0; + + Iterator iter = list.iterator(); + SumPoint first = iter.next(); + boolean conditionMet = false; + + // now sum up the preceeding points + while(iter.hasNext()) { + SumPoint next = iter.next(); + result += (Double) next.val; + count++; + if (!isTimeUnit && count >= numPoints) { + conditionMet = true; + break; + } else if (isTimeUnit && ((first.ts - next.ts) > numPoints)) { + conditionMet = true; + break; + } + } + + if (!conditionMet || count == 0) { + return 0; + } + + return result/count; + } + + class SumPoint { + long ts; + Object val; + public SumPoint(long ts, Object val) { + this.ts = ts; + this.val = val; + } + } + } +} From 052de63ee8e3c946d27e5a3593bf05b128a9c975 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sat, 31 Oct 2015 19:05:24 -0700 Subject: [PATCH 021/233] Fix up the MovingAverage expression function. - Fix an off-by-one error - Fix the timed window so it kicks out the first value as we don't know what the previous timestamp was so we don't know if it should be in our window. - Reduce the linked list when a value falls out of the window - Add OpenTSDB time units to the window size parser - Force it to output in doubles to avoid flip-flopping integer and double computations, possibly losing precision - Add comments and cleanup formatting - Add Unit tests Signed-off-by: Chris Larsen --- Makefile.am | 2 + src/query/expression/MovingAverage.java | 377 ++++++++++------ test/query/expression/TestMovingAverage.java | 438 +++++++++++++++++++ 3 files changed, 687 insertions(+), 130 deletions(-) create mode 100644 test/query/expression/TestMovingAverage.java diff --git a/Makefile.am b/Makefile.am index 0aae69b4c9..5f97368a75 100644 --- a/Makefile.am +++ b/Makefile.am @@ -74,6 +74,7 @@ tsdb_SRC := \ src/meta/TSUIDQuery.java \ src/meta/UIDMeta.java \ src/query/QueryUtil.java \ + src/query/expression/MovingAverage.java \ src/query/expression/PostAggregatedDataPoints.java \ src/query/filter/TagVFilter.java \ src/query/filter/TagVLiteralOrFilter.java \ @@ -219,6 +220,7 @@ test_SRC := \ test/meta/TestTSMeta.java \ test/meta/TestTSUIDQuery.java \ test/meta/TestUIDMeta.java \ + test/query/expression/TestMovingAverage.java \ test/query/expression/TestPostAggregatedDataPoints.java \ test/query/filter/TestTagVFilter.java \ test/query/filter/TestTagVLiteralOrFilter.java \ diff --git a/src/query/expression/MovingAverage.java b/src/query/expression/MovingAverage.java index d9bda06a26..8366a492d8 100644 --- a/src/query/expression/MovingAverage.java +++ b/src/query/expression/MovingAverage.java @@ -1,3 +1,15 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . package net.opentsdb.query.expression; import java.util.ArrayList; @@ -11,225 +23,330 @@ import net.opentsdb.core.Aggregators; import net.opentsdb.core.DataPoint; import net.opentsdb.core.DataPoints; +import net.opentsdb.core.IllegalDataException; import net.opentsdb.core.MutableDataPoint; import net.opentsdb.core.SeekableView; import net.opentsdb.core.TSQuery; import net.opentsdb.core.Aggregators.Interpolation; -import com.google.common.collect.Lists; - +/** + * Implements a moving average function windowed on either the number of + * data points or a unit of time. + * @since 2.3 + */ public class MovingAverage implements Expression { - + @Override - public DataPoints[] evaluate(final TSQuery data_query, List queryResults, List params) { - if (queryResults == null || queryResults.isEmpty()) { + public DataPoints[] evaluate(final TSQuery data_query, + final List query_results, final List params) { + if (data_query == null) { + throw new IllegalArgumentException("Missing time series query"); + } + if (query_results == null || query_results.isEmpty()) { return new DataPoints[]{}; } - if (params == null || params.isEmpty()) { - throw new NullPointerException("Need aggregation window for moving average"); + throw new IllegalArgumentException("Missing moving average window size"); } String param = params.get(0); - if (param == null || param.length() == 0) { - throw new NullPointerException("Invalid window='" + param + "'"); + if (param == null || param.isEmpty()) { + throw new IllegalArgumentException("Missing moving average window size"); } - param = param.trim(); - long numPoints = -1; - boolean isTimeUnit = false; - if (param.matches("[0-9]+")) { - numPoints = Integer.parseInt(param); + long condition = -1; + boolean is_time_unit = false; + if (param.matches("^[0-9]+$")) { + try { + condition = Integer.parseInt(param); + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException( + "Invalid parameter, must be an integer", nfe); + } } else if (param.startsWith("'") && param.endsWith("'")) { - numPoints = parseParam(param); - isTimeUnit = true; + condition = parseParam(param); + is_time_unit = true; + } else { + throw new IllegalArgumentException("Unparseable window size: " + param); } - - if (numPoints <= 0) { - throw new RuntimeException("numPoints <= 0"); + if (condition <= 0) { + throw new IllegalArgumentException("Moving average window must be an " + + "integer greater than zero"); } - int size = 0; - for (DataPoints[] results: queryResults) { - size = size + results.length; + int num_results = 0; + for (final DataPoints[] results : query_results) { + num_results += results.length; } - PostAggregatedDataPoints[] seekablePoints = new PostAggregatedDataPoints[size]; - int ix=0; + final PostAggregatedDataPoints[] post_agg_results = + new PostAggregatedDataPoints[num_results]; + int ix = 0; // one or more queries (m=...&m=...&m=...) - for (DataPoints[] results: queryResults) { + for (final DataPoints[] sub_query_result : query_results) { // group bys (m=sum:foo{host=*}) - for (DataPoints dpoints: results) { - List mutablePoints = new ArrayList(); - for (DataPoint point: dpoints) { - mutablePoints.add(point.isInteger() ? - MutableDataPoint.ofLongValue(point.timestamp(), point.longValue()) - : MutableDataPoint.ofDoubleValue(point.timestamp(), point.doubleValue())); + for (final DataPoints dps: sub_query_result) { + // TODO(cl) - Avoid iterating and copying if we can help it. We should + // be able to pass the original DataPoints object to the seekable view + // and then iterate through it. + final List mutable_points = new ArrayList(); + for (final DataPoint point: dps) { + // avoid flip-flopping between integers and floats, always use double + // for average. + mutable_points.add( + MutableDataPoint.ofDoubleValue(point.timestamp(), point.toDouble())); } - seekablePoints[ix++] = new PostAggregatedDataPoints(dpoints, - mutablePoints.toArray(new DataPoint[mutablePoints.size()])); + post_agg_results[ix++] = new PostAggregatedDataPoints(dps, + mutable_points.toArray(new DataPoint[mutable_points.size()])); } } - SeekableView[] views = new SeekableView[size]; - for (int i=0; i points = Lists.newArrayList(); + // TODO(cl) - here's a good place to return the AggregationIterators instead + // of processing them in situ and making copies + final List points = new ArrayList(); while (view.hasNext()) { DataPoint mdp = view.next(); - points.add(mdp.isInteger() ? - MutableDataPoint.ofLongValue(mdp.timestamp(), mdp.longValue()) : - MutableDataPoint.ofDoubleValue(mdp.timestamp(), mdp.doubleValue())); + points.add(MutableDataPoint.ofDoubleValue(mdp.timestamp(), mdp.toDouble())); } - if (queryResults.size() > 0 && queryResults.get(0).length > 0) { - return new DataPoints[]{new PostAggregatedDataPoints(queryResults.get(0)[0], + if (query_results.size() > 0 && query_results.get(0).length > 0) { + return new DataPoints[]{new PostAggregatedDataPoints(query_results.get(0)[0], points.toArray(new DataPoint[points.size()]))}; } else { return new DataPoints[]{}; } } - public long parseParam(String param) { - char[] chars = param.toCharArray(); - int tuIndex = 0; + /** + * Parses the parameter string to fetch the window size + *

+ * Package private for UTs + * @param param The string to parse + * @return The window size (number of points or a unit of time in ms) + */ + long parseParam(final String param) { + if (param == null || param.isEmpty()) { + throw new IllegalArgumentException( + "Window parameter may not be null or empty"); + } + final char[] chars = param.toCharArray(); + int idx = 0; for (int c = 1; c < chars.length; c++) { if (Character.isDigit(chars[c])) { - tuIndex++; + idx++; } else { break; } } - - if (tuIndex == 0) { - throw new RuntimeException("Invalid Parameter: " + param); + if (idx < 1) { + throw new IllegalArgumentException("Invalid moving window parameter: " + + param); } - int time = Integer.parseInt(param.substring(1, tuIndex + 1)); - String unit = param.substring(tuIndex+1, param.length() - 1); - - if ("min".equals(unit)) { - return TimeUnit.MILLISECONDS.convert(time, TimeUnit.MINUTES); - } else if ("hr".equals(unit)) { - return TimeUnit.MILLISECONDS.convert(time, TimeUnit.HOURS); - } else if ("sec".equals(unit)) { - return TimeUnit.MILLISECONDS.convert(time, TimeUnit.SECONDS); - } else { - throw new RuntimeException("unknown time unit=" + unit); + try { + final int time = Integer.parseInt(param.substring(1, idx + 1)); + final String unit = param.substring(idx + 1, param.length() - 1); + + // TODO(CL) - add a Graphite unit parser to DateTime for this kind of conversion + if ("day".equals(unit) || "d".equals(unit)) { + return TimeUnit.MILLISECONDS.convert(time, TimeUnit.DAYS); + } else if ("hr".equals(unit) || "hour".equals(unit) || "h".equals(unit)) { + return TimeUnit.MILLISECONDS.convert(time, TimeUnit.HOURS); + } else if ("min".equals(unit) || "m".equals(unit)) { + return TimeUnit.MILLISECONDS.convert(time, TimeUnit.MINUTES); + } else if ("sec".equals(unit) || "s".equals(unit)) { + return TimeUnit.MILLISECONDS.convert(time, TimeUnit.SECONDS); + } else { + throw new IllegalArgumentException("Unknown time unit=" + unit + + " in window=" + param); + } + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException("Unable to parse moving window " + + "parameter: " + param, nfe); } - } @Override - public String writeStringField(List queryParams, String innerExpression) { - return "movingAverage(" + innerExpression + ")"; + public String writeStringField(final List query_params, + final String inner_expression) { + return "movingAverage(" + inner_expression + ")"; } + /** + * An aggregator that expects a single data point for each iteration. The + * values are prepended to a linked list. Next it iterates over the list until + * it either runs out of values (and returns a 0 with the proper timestamp) or + * returns the average of all values in the given window (time or number based). + *

+ * Package private for unit testing + */ static final class MovingAverageAggregator extends Aggregator { - private LinkedList list = new LinkedList(); - private final long numPoints; - private final boolean isTimeUnit; + /** The individual values in the window */ + private final LinkedList accumulation; + /** The condition to satisfy, either a time unit or # of data points */ + private final long condition; + /** Whether or not the condition is a time unit or the # of data points */ + private final boolean is_time_unit; + /** Sentinel used to kick out the first timed window value */ + private boolean window_started; - public MovingAverageAggregator(final Interpolation method, final String name, long numPoints, boolean isTimeUnit) { + /** + * Ctor for this implementation + * @param method The interpolation method to use (ignored) + * @param name The name of this aggregator + * @param condition The windowing condition + * @param is_time_unit Whether or not the condition is a time unit or + * the # of data points + */ + public MovingAverageAggregator(final Interpolation method, final String name, + final long condition, final boolean is_time_unit) { super(method, name); - this.numPoints = numPoints; - this.isTimeUnit = isTimeUnit; + this.condition = condition; + this.is_time_unit = is_time_unit; + accumulation = new LinkedList(); } @Override public long runLong(final Longs values) { - long sum = values.nextLongValue(); - while (values.hasNextValue()) { - sum += values.nextLongValue(); + final long value = values.nextLongValue(); + if (values.hasNextValue()) { + throw new IllegalDataException( + "There should only be one value in " + values); } + final long ts = ((DataPoint) values).timestamp(); + accumulation.addFirst(MutableDataPoint.ofLongValue(ts, value)); - if (values instanceof DataPoint) { - long ts = ((DataPoint) values).timestamp(); - list.addFirst(new SumPoint(ts, sum)); + // for timed windows we need to skip the first data point in the series + // as we have no idea what the previous value's timestamp was. + if (is_time_unit && !window_started) { + window_started = true; + return 0; } - - long result=0; int count=0; - - Iterator iter = list.iterator(); - SumPoint first = iter.next(); - boolean conditionMet = false; - - // now sum up the preceeding points + + long sum = 0; + int count = 0; + final Iterator iter = accumulation.iterator(); + boolean condition_met = false; + long time_window_cumulation = 0; // how many ms are in our window + long last_ts = -1; // the timestamp of the previous dp + + // now sum up the preceding points while(iter.hasNext()) { - SumPoint next = iter.next(); - result += (Long) next.val; + final DataPoint dp = iter.next(); + if (is_time_unit) { + if (last_ts < 0) { + last_ts = dp.timestamp(); + } else { + time_window_cumulation += last_ts - dp.timestamp(); + last_ts = dp.timestamp(); + if (time_window_cumulation >= condition) { + condition_met = true; + break; + } + } + } + // cast to long if we dumped a double in there + sum += dp.isInteger() ? dp.longValue() : dp.doubleValue(); count++; - if (!isTimeUnit && count >= numPoints) { - conditionMet = true; - break; - } else if (isTimeUnit && ((first.ts - next.ts) > numPoints)) { - conditionMet = true; + if (!is_time_unit && count >= condition) { + condition_met = true; break; } } + while (iter.hasNext()) { + // should drop the last entry in the linked list to avoid accumulating + // everything in memory + iter.next(); + iter.remove(); + } - if (!conditionMet || count == 0) { + if (!condition_met || count == 0) { return 0; } - - return result/count; + return sum / count; } @Override public double runDouble(Doubles values) { - double sum = values.nextDoubleValue(); - while (values.hasNextValue()) { - sum += values.nextDoubleValue(); + final double value = values.nextDoubleValue(); + if (values.hasNextValue()) { + throw new IllegalDataException( + "There should only be one value in " + values); } - - if (values instanceof DataPoint) { - long ts = ((DataPoint) values).timestamp(); - list.addFirst(new SumPoint(ts, sum)); + final long ts = ((DataPoint) values).timestamp(); + accumulation.addFirst(MutableDataPoint.ofDoubleValue(ts, value)); + + // for timed windows we need to skip the first data point in the series + // as we have no idea what the previous value's timestamp was. + if (is_time_unit && !window_started) { + window_started = true; + return 0; } - - double result=0; int count=0; - - Iterator iter = list.iterator(); - SumPoint first = iter.next(); - boolean conditionMet = false; - - // now sum up the preceeding points + + double sum = 0; + int count = 0; + final Iterator iter = accumulation.iterator(); + boolean condition_met = false; + long time_window_cumulation = 0; // how many ms are in our window + long last_ts = -1; // the timestamp of the previous dp + + // now sum up the preceding points while(iter.hasNext()) { - SumPoint next = iter.next(); - result += (Double) next.val; - count++; - if (!isTimeUnit && count >= numPoints) { - conditionMet = true; - break; - } else if (isTimeUnit && ((first.ts - next.ts) > numPoints)) { - conditionMet = true; + final DataPoint dp = iter.next(); + + if (is_time_unit) { + if (last_ts < 0) { + last_ts = dp.timestamp(); + } else { + time_window_cumulation += last_ts - dp.timestamp(); + last_ts = dp.timestamp(); + if (time_window_cumulation >= condition) { + condition_met = true; + break; + } + } + } + + // cast to double if we dumped a long in there + final double v = dp.isInteger() ? dp.longValue() : dp.doubleValue(); + if (!Double.isNaN(v)) { + // skip NaNs to avoid NaNing everything in the window. + sum += v; + count++; + } + + if (!is_time_unit && count >= condition) { + condition_met = true; break; } } - - if (!conditionMet || count == 0) { - return 0; + + while (iter.hasNext()) { + // should drop the last entry in the linked list to avoid accumulating + // everything in memory + iter.next(); + iter.remove(); } - return result/count; - } - - class SumPoint { - long ts; - Object val; - public SumPoint(long ts, Object val) { - this.ts = ts; - this.val = val; + if (!condition_met || count == 0) { + return 0; } + return sum/count; } } } diff --git a/test/query/expression/TestMovingAverage.java b/test/query/expression/TestMovingAverage.java new file mode 100644 index 0000000000..3791c6f0f5 --- /dev/null +++ b/test/query/expression/TestMovingAverage.java @@ -0,0 +1,438 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import net.opentsdb.core.DataPoint; +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.SeekableView; +import net.opentsdb.core.SeekableViewsForTest; +import net.opentsdb.core.TSQuery; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +@RunWith(PowerMockRunner.class) +@PowerMockIgnore({"javax.management.*", "javax.xml.*", + "ch.qos.*", "org.slf4j.*", + "com.sum.*", "org.xml.*"}) +@PrepareForTest({ TSQuery.class }) +public class TestMovingAverage { + private static long START_TIME = 1356998400000L; + private static int INTERVAL = 60000; + private static int NUM_POINTS = 5; + private static String METRIC = "sys.cpu"; + + private TSQuery data_query; + private SeekableView view; + private DataPoints dps; + private DataPoints[] group_bys; + private List query_results; + private List params; + private MovingAverage func; + + @Before + public void before() throws Exception { + view = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 1, 1); + data_query = mock(TSQuery.class); + when(data_query.startTime()).thenReturn(START_TIME); + when(data_query.endTime()).thenReturn(START_TIME + (INTERVAL * NUM_POINTS)); + + dps = PowerMockito.mock(DataPoints.class); + when(dps.iterator()).thenReturn(view); + when(dps.metricName()).thenReturn(METRIC); + + group_bys = new DataPoints[] { dps }; + + query_results = new ArrayList(1); + query_results.add(group_bys); + + params = new ArrayList(1); + func = new MovingAverage(); + } + + @Test + public void evaluateWindow1dps() throws Exception { + params.add("1"); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(1, results.length); + assertEquals(METRIC, results[0].metricName()); + + long ts = START_TIME; + double v = 1; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertFalse(dp.isInteger()); + assertEquals(v, dp.doubleValue(), 0.001); + ts += INTERVAL; + v += 1; + } + } + + @Test + public void evaluateWindow2dps() throws Exception { + params.add("2"); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(1, results.length); + assertEquals(METRIC, results[0].metricName()); + + long ts = START_TIME; + double v = 0; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertFalse(dp.isInteger()); + assertEquals(v, dp.doubleValue(), 0.001); + ts += INTERVAL; + if (v < 1) { + v = 1.5; + } else { + v += 1; + } + } + } + + @Test + public void evaluateWindow5dps() throws Exception { + params.add("5"); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(1, results.length); + assertEquals(METRIC, results[0].metricName()); + + long ts = START_TIME; + double v = 0; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertFalse(dp.isInteger()); + assertEquals(v, dp.doubleValue(), 0.001); + ts += INTERVAL; + if (ts == 1356998640000L) { + v = 3.0; + } + } + } + + @Test + public void evaluateWindow6dps() throws Exception { + params.add("6"); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(1, results.length); + assertEquals(METRIC, results[0].metricName()); + + long ts = START_TIME; + double v = 0; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertFalse(dp.isInteger()); + assertEquals(v, dp.doubleValue(), 0.001); + ts += INTERVAL; + } + } + + @Test + public void evaluateWindow1min() throws Exception { + params.add("'1min'"); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(1, results.length); + assertEquals(METRIC, results[0].metricName()); + + long ts = START_TIME; + double v = 0; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertFalse(dp.isInteger()); + assertEquals(v, dp.doubleValue(), 0.001); + ts += INTERVAL; + if (v < 1) { + v = 2; + } else { + v += 1; + } + } + } + + @Test + public void evaluateWindow2min() throws Exception { + params.add("'2min'"); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(1, results.length); + assertEquals(METRIC, results[0].metricName()); + + long ts = START_TIME; + double v = 0; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertFalse(dp.isInteger()); + assertEquals(v, dp.doubleValue(), 0.001); + ts += INTERVAL; + if (ts == 1356998520000L) { + v = 2.5; + } else if (v > 0) { + v += 1; + } + } + } + + @Test + public void evaluateWindow3min() throws Exception { + params.add("'3min'"); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(1, results.length); + assertEquals(METRIC, results[0].metricName()); + + long ts = START_TIME; + double v = 0; + for (DataPoint dp : results[0]) { + System.out.println(dp.timestamp() + " : " + dp.doubleValue()); + assertEquals(ts, dp.timestamp()); + assertFalse(dp.isInteger()); + assertEquals(v, dp.doubleValue(), 0.001); + ts += INTERVAL; + if (ts == 1356998580000L) { + v = 3; + } else if (v > 0) { + v += 1; + } + } + } + + @Test + public void evaluateWindow4min() throws Exception { + params.add("'4min'"); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(1, results.length); + assertEquals(METRIC, results[0].metricName()); + + long ts = START_TIME; + double v = 0; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertFalse(dp.isInteger()); + assertEquals(v, dp.doubleValue(), 0.001); + ts += INTERVAL; + if (ts == 1356998640000L) { + v = 3.5; + } + } + } + + @Test + public void evaluateWindow5min() throws Exception { + params.add("'5min'"); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(1, results.length); + assertEquals(METRIC, results[0].metricName()); + + long ts = START_TIME; + double v = 0; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertFalse(dp.isInteger()); + assertEquals(v, dp.doubleValue(), 0.001); + ts += INTERVAL; + } + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateNullQuery() throws Exception { + params.add("1"); + func.evaluate(null, query_results, params); + } + + @Test + public void evaluateNullResults() throws Exception { + params.add("1"); + final DataPoints[] results = func.evaluate(data_query, null, params); + assertEquals(0, results.length); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateNullParams() throws Exception { + func.evaluate(data_query, query_results, null); + } + + @Test + public void evaluateEmptyResults() throws Exception { + params.add("1"); + final DataPoints[] results = func.evaluate(data_query, + Collections.emptyList(), params); + assertEquals(0, results.length); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateEmptyParams() throws Exception { + func.evaluate(data_query, query_results, params); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateWindowIsZeroDataPoints() throws Exception { + params.add("0"); + func.evaluate(data_query, query_results, params); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateWindowIsZeroTime() throws Exception { + params.add("'0sec'"); + func.evaluate(data_query, query_results, params); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateWindowTimedMissingQuotes() throws Exception { + params.add("60sec"); + func.evaluate(data_query, query_results, params); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateWindowNull() throws Exception { + params.add(null); + func.evaluate(data_query, query_results, params); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateWindowEmpty() throws Exception { + params.add(""); + func.evaluate(data_query, query_results, params); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateWindowUnknown() throws Exception { + params.add("somethingelse"); + func.evaluate(data_query, query_results, params); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateWindowNotFirstParam() throws Exception { + params.add("somethingelse"); + params.add("60"); + func.evaluate(data_query, query_results, params); + } + + @Test + public void writeStringField() throws Exception { + params.add("1"); + assertEquals("movingAverage(inner_expression)", + func.writeStringField(params, "inner_expression")); + assertEquals("movingAverage(null)", func.writeStringField(params, null)); + assertEquals("movingAverage()", func.writeStringField(params, "")); + assertEquals("movingAverage(inner_expression)", + func.writeStringField(null, "inner_expression")); + } + + @Test + public void parseParam() throws Exception { + // second + assertEquals(1000, func.parseParam("'1sec'")); + assertEquals(1000, func.parseParam("'1s'")); + assertEquals(5000, func.parseParam("'5sec'")); + assertEquals(5000, func.parseParam("'5s'")); + + // minute + assertEquals(60000, func.parseParam("'1min'")); + assertEquals(60000, func.parseParam("'1m'")); + assertEquals(300000, func.parseParam("'5min'")); + assertEquals(300000, func.parseParam("'5m'")); + + // hour + assertEquals(3600000, func.parseParam("'1hr")); + assertEquals(3600000, func.parseParam("'1h'")); + assertEquals(3600000, func.parseParam("'1hour'")); + assertEquals(18000000, func.parseParam("'5hr'")); + assertEquals(18000000, func.parseParam("'5h'")); + assertEquals(18000000, func.parseParam("'5hour'")); + + // day + assertEquals(86400000, func.parseParam("'1day'")); + assertEquals(86400000, func.parseParam("'1d'")); + assertEquals(432000000, func.parseParam("'5day'")); + assertEquals(432000000, func.parseParam("'5d'")); + + // TODO - fix it, closing with a 1 seems to work instead of a ' + assertEquals(1000, func.parseParam("'1sec1")); + + // missing quotes + try { + func.parseParam("'1sec"); + fail("Expected an IllegalArgumentException"); + } catch (IllegalArgumentException e) { } + try { + func.parseParam("1sec'"); + fail("Expected an IllegalArgumentException"); + } catch (IllegalArgumentException e) { } + try { + func.parseParam("1sec"); + fail("Expected an IllegalArgumentException"); + } catch (IllegalArgumentException e) { } + + // no numbers or units + try { + func.parseParam("'sec'"); + fail("Expected an IllegalArgumentException"); + } catch (IllegalArgumentException e) { } + try { + func.parseParam("'60'"); + fail("Expected an IllegalArgumentException"); + } catch (IllegalArgumentException e) { } + + // null or empty or short + try { + func.parseParam(null); + fail("Expected an IllegalArgumentException"); + } catch (IllegalArgumentException e) { } + try { + func.parseParam(""); + fail("Expected an IllegalArgumentException"); + } catch (IllegalArgumentException e) { } + try { + func.parseParam("'"); + fail("Expected an IllegalArgumentException"); + } catch (IllegalArgumentException e) { } + + // floating point + try { + func.parseParam("'1.5sec'"); + fail("Expected an IllegalArgumentException"); + } catch (IllegalArgumentException e) { } + } +} From 7828583568cc6bf85c5c19efd307b9588c5435d4 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sat, 31 Oct 2015 16:07:12 -0700 Subject: [PATCH 022/233] Make the runDouble() and runLong() abstract methods public in the Aggregator class so we can implement it in other locations. Signed-off-by: Chris Larsen --- src/core/Aggregator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/Aggregator.java b/src/core/Aggregator.java index baf1721b62..0b6a1a7818 100644 --- a/src/core/Aggregator.java +++ b/src/core/Aggregator.java @@ -92,14 +92,14 @@ public interface Doubles { * @param values The sequence to aggregate. * @return The aggregated value. */ - abstract long runLong(Longs values); + public abstract long runLong(Longs values); /** * Aggregates a sequence of {@code double}s. * @param values The sequence to aggregate. * @return The aggregated value. */ - abstract double runDouble(Doubles values); + public abstract double runDouble(Doubles values); /** * Returns the interpolation method to use when working with data points From 424d34b236445c2128f480065f2063628aac6cc1 Mon Sep 17 00:00:00 2001 From: Arjun Satish Date: Sat, 31 Oct 2015 16:14:55 -0700 Subject: [PATCH 023/233] Add the Expression interface used to implement Graphite expressions. Signed-off-by: Chris Larsen --- src/query/expression/Expression.java | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/query/expression/Expression.java diff --git a/src/query/expression/Expression.java b/src/query/expression/Expression.java new file mode 100644 index 0000000000..f8d799c86c --- /dev/null +++ b/src/query/expression/Expression.java @@ -0,0 +1,27 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import java.util.List; + +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.TSQuery; + +public interface Expression { + + public DataPoints[] evaluate(TSQuery data_query, + List results, List params); + + public String writeStringField(List params, String inner_expression); + +} From 256ba00ca9bde854ae4135c18d15acf57dc9b453 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sat, 31 Oct 2015 16:18:09 -0700 Subject: [PATCH 024/233] Add some notes to the Expression interface. Signed-off-by: Chris Larsen --- src/query/expression/Expression.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/query/expression/Expression.java b/src/query/expression/Expression.java index f8d799c86c..d7d15acc1d 100644 --- a/src/query/expression/Expression.java +++ b/src/query/expression/Expression.java @@ -17,11 +17,30 @@ import net.opentsdb.core.DataPoints; import net.opentsdb.core.TSQuery; +/** + * The interface for various expressions/functions used when querying OpenTSDB. + * @since 2.3 + */ public interface Expression { + /** + * Computes a set of results given the results of a {@link TSQuery} that may + * include multiple metrics and/or group by result sets. + * @param data_query The original query from the user + * @param results The results of the query + * @param params Parameters parsed from the expression endpoint related to + * the implementing function + * @return An array of data points resulting from the implementation + */ public DataPoints[] evaluate(TSQuery data_query, List results, List params); + /** + * TODO - document me! + * @param params + * @param inner_expression + * @return + */ public String writeStringField(List params, String inner_expression); } From a05de9afe87162568d2e269a7c7f610ded049905 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sat, 31 Oct 2015 20:15:20 -0700 Subject: [PATCH 025/233] Fix up the MovingAverage function to handle group bys and sub queries. Previously it tried to aggregate group bys which doesn't make sense for a moving average. Signed-off-by: Chris Larsen --- src/query/expression/MovingAverage.java | 49 ++++++------ test/query/expression/TestMovingAverage.java | 78 ++++++++++++++++++++ 2 files changed, 99 insertions(+), 28 deletions(-) diff --git a/src/query/expression/MovingAverage.java b/src/query/expression/MovingAverage.java index 8366a492d8..5a86b90c7c 100644 --- a/src/query/expression/MovingAverage.java +++ b/src/query/expression/MovingAverage.java @@ -54,7 +54,7 @@ public DataPoints[] evaluate(final TSQuery data_query, throw new IllegalArgumentException("Missing moving average window size"); } param = param.trim(); - + long condition = -1; boolean is_time_unit = false; if (param.matches("^[0-9]+$")) { @@ -103,35 +103,28 @@ public DataPoints[] evaluate(final TSQuery data_query, } } - final SeekableView[] views = new SeekableView[num_results]; - for (int i=0; i points = new ArrayList(); - while (view.hasNext()) { - DataPoint mdp = view.next(); - points.add(MutableDataPoint.ofDoubleValue(mdp.timestamp(), mdp.toDouble())); - } - - if (query_results.size() > 0 && query_results.get(0).length > 0) { - return new DataPoints[]{new PostAggregatedDataPoints(query_results.get(0)[0], - points.toArray(new DataPoint[points.size()]))}; - } else { - return new DataPoints[]{}; + final DataPoints[] results = new DataPoints[num_results]; + for (int i = 0; i < num_results; i++) { + final Aggregator moving_average = new MovingAverageAggregator( + Aggregators.Interpolation.LERP, "movingAverage", + condition, is_time_unit); + final SeekableView[] metrics_groups = new SeekableView[] { + post_agg_results[i].iterator() }; + final SeekableView view = new AggregationIterator(metrics_groups, + data_query.startTime(), data_query.endTime(), + moving_average, + Aggregators.Interpolation.LERP, false); + final List points = new ArrayList(); + while (view.hasNext()) { + final DataPoint mdp = view.next(); + points.add(MutableDataPoint.ofDoubleValue(mdp.timestamp(), mdp.toDouble())); + } + results[i] = new PostAggregatedDataPoints(post_agg_results[i], + points.toArray(new DataPoint[points.size()])); } + return results; } - + /** * Parses the parameter string to fetch the window size *

diff --git a/test/query/expression/TestMovingAverage.java b/test/query/expression/TestMovingAverage.java index 3791c6f0f5..4a59cf38a1 100644 --- a/test/query/expression/TestMovingAverage.java +++ b/test/query/expression/TestMovingAverage.java @@ -275,6 +275,84 @@ public void evaluateWindow5min() throws Exception { } } + @Test + public void evaluateGroupBy() throws Exception { + params.add("1"); + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + group_bys = new DataPoints[] { dps, dps2 }; + query_results.clear(); + query_results.add(group_bys); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(2, results.length); + assertEquals(METRIC, results[0].metricName()); + assertEquals("sys.mem", results[1].metricName()); + + long ts = START_TIME; + double v = 1; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertFalse(dp.isInteger()); + assertEquals(v, dp.doubleValue(), 0.001); + ts += INTERVAL; + v += 1; + } + + ts = START_TIME; + v = 10; + for (DataPoint dp : results[1]) { + assertEquals(ts, dp.timestamp()); + assertFalse(dp.isInteger()); + assertEquals(v, dp.doubleValue(), 0.001); + ts += INTERVAL; + v += 1; + } + } + + @Test + public void evaluateSubQuery() throws Exception { + params.add("1"); + + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + DataPoints[] group_bys2 = new DataPoints[] { dps2 }; + query_results.add(group_bys2); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(2, results.length); + assertEquals(METRIC, results[0].metricName()); + assertEquals("sys.mem", results[1].metricName()); + + long ts = START_TIME; + double v = 1; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertFalse(dp.isInteger()); + assertEquals(v, dp.doubleValue(), 0.001); + ts += INTERVAL; + v += 1; + } + + ts = START_TIME; + v = 10; + for (DataPoint dp : results[1]) { + assertEquals(ts, dp.timestamp()); + assertFalse(dp.isInteger()); + assertEquals(v, dp.doubleValue(), 0.001); + ts += INTERVAL; + v += 1; + } + } + @Test (expected = IllegalArgumentException.class) public void evaluateNullQuery() throws Exception { params.add("1"); From 36f6bc7a7035961d0ea39f15030c2221a89d54e9 Mon Sep 17 00:00:00 2001 From: Arjun Satish Date: Sat, 31 Oct 2015 19:46:21 -0700 Subject: [PATCH 026/233] Add the HighestMax function for use in top-n queries to determine which time series had the highest maximum value. Signed-off-by: Chris Larsen --- src/query/expression/HighestMax.java | 239 +++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 src/query/expression/HighestMax.java diff --git a/src/query/expression/HighestMax.java b/src/query/expression/HighestMax.java new file mode 100644 index 0000000000..664ee8bbd3 --- /dev/null +++ b/src/query/expression/HighestMax.java @@ -0,0 +1,239 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2010-2012 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +import net.opentsdb.core.AggregationIterator; +import net.opentsdb.core.Aggregator; +import net.opentsdb.core.Aggregators; +import net.opentsdb.core.DataPoint; +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.MutableDataPoint; +import net.opentsdb.core.SeekableView; +import net.opentsdb.core.TSQuery; +import net.opentsdb.core.Aggregators.Interpolation; + +public class HighestMax implements Expression { + + @Override + public DataPoints[] evaluate(TSQuery query, List queryResults, + List params) { + if (queryResults == null || queryResults.isEmpty()) { + throw new NullPointerException("Query results cannot be empty"); + } + + if (params == null || params.isEmpty()) { + throw new NullPointerException("Need aggregation window for moving average"); + } + + String param = params.get(0); + if (param == null || param.length() == 0) { + throw new NullPointerException("Invalid window='" + param + "'"); + } + + int k = Integer.parseInt(param.trim()); + + int size = 0; + for (DataPoints[] results: queryResults) { + size = size + results.length; + } + + PostAggregatedDataPoints[] seekablePoints = new PostAggregatedDataPoints[size]; + int ix=0; + for (DataPoints[] results: queryResults) { + for (DataPoints dpoints: results) { + List mutablePoints = new ArrayList(); + for (DataPoint point: dpoints) { + mutablePoints.add(point.isInteger() ? + MutableDataPoint.ofLongValue(point.timestamp(), point.longValue()) + : MutableDataPoint.ofDoubleValue(point.timestamp(), point.doubleValue())); + } + seekablePoints[ix++] = new PostAggregatedDataPoints(dpoints, + mutablePoints.toArray(new DataPoint[mutablePoints.size()])); + } + } + + if (k >= size) { + return seekablePoints; + } + + SeekableView[] views = new SeekableView[size]; + for (int i=0; i() { + @Override + public int compare(Entry o1, Entry o2) { + // we want in descending order + return -1 * Double.compare(o1.val, o2.val); + } + }); + + DataPoints[] results = new DataPoints[k]; + for (int i=0; i queryParams, String innerExpression) { + return "highestMax(" + innerExpression + ")"; + } + + + static class MaxCacheAggregator extends Aggregator { + + private final int size; + private final long[] maxLongs; + private final double[] maxDoubles; + private boolean hasLongs = false; + private boolean hasDoubles = false; + + private long start; + private long end; + + public MaxCacheAggregator(Interpolation method, String name, int size, + long startTimeInMillis, long endTimeInMillis) { + super(method, name); + this.size = size; + this.start = startTimeInMillis; + this.end = endTimeInMillis; + + this.maxLongs = new long[size]; + this.maxDoubles = new double[size]; + + for (int i=0; i end) { + return 0; + } + } + + long[] longs = new long[size]; + int ix = 0; + longs[ix++] = values.nextLongValue(); + while (values.hasNextValue()) { + longs[ix++] = values.nextLongValue(); + } + + for (int i=0; i end) { + return 0; + } + } + + double[] doubles = new double[size]; + int ix = 0; + doubles[ix++] = values.nextDoubleValue(); + while (values.hasNextValue()) { + doubles[ix++] = values.nextDoubleValue(); + } + for (int i=0; i Date: Sat, 31 Oct 2015 21:00:17 -0700 Subject: [PATCH 027/233] Clean up the HighestMax expression function: - Fix a bug when the result set is smaller than the topn value - Comments and formatting Signed-off-by: Chris Larsen --- Makefile.am | 2 + src/query/expression/HighestMax.java | 240 +++++++++++------- test/query/expression/TestHighestMax.java | 288 ++++++++++++++++++++++ 3 files changed, 436 insertions(+), 94 deletions(-) create mode 100644 test/query/expression/TestHighestMax.java diff --git a/Makefile.am b/Makefile.am index 5f97368a75..df8051b7f2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -74,6 +74,7 @@ tsdb_SRC := \ src/meta/TSUIDQuery.java \ src/meta/UIDMeta.java \ src/query/QueryUtil.java \ + src/query/expression/HighestMax.java \ src/query/expression/MovingAverage.java \ src/query/expression/PostAggregatedDataPoints.java \ src/query/filter/TagVFilter.java \ @@ -220,6 +221,7 @@ test_SRC := \ test/meta/TestTSMeta.java \ test/meta/TestTSUIDQuery.java \ test/meta/TestUIDMeta.java \ + test/query/expression/TestHighestMax.java \ test/query/expression/TestMovingAverage.java \ test/query/expression/TestPostAggregatedDataPoints.java \ test/query/filter/TestTagVFilter.java \ diff --git a/src/query/expression/HighestMax.java b/src/query/expression/HighestMax.java index 664ee8bbd3..5ba67cbcd9 100644 --- a/src/query/expression/HighestMax.java +++ b/src/query/expression/HighestMax.java @@ -27,151 +27,202 @@ import net.opentsdb.core.TSQuery; import net.opentsdb.core.Aggregators.Interpolation; +/** + * Implements top-n functionality by iterating over each of the time series, + * finding the max value for each time series within the query time range, + * and up to "n" time series with the highest values, sorted in descending + * order. + * @since 2.3 + */ public class HighestMax implements Expression { @Override - public DataPoints[] evaluate(TSQuery query, List queryResults, - List params) { - if (queryResults == null || queryResults.isEmpty()) { - throw new NullPointerException("Query results cannot be empty"); + public DataPoints[] evaluate(final TSQuery data_query, + final List query_results, final List params) { + if (data_query == null) { + throw new IllegalArgumentException("Missing time series query"); } - + if (query_results == null || query_results.isEmpty()) { + return new DataPoints[]{}; + } + // TODO(cl) - allow for empty top-n maybe? Just sort the results by max? if (params == null || params.isEmpty()) { - throw new NullPointerException("Need aggregation window for moving average"); + throw new IllegalArgumentException("Need aggregation window for moving average"); } String param = params.get(0); if (param == null || param.length() == 0) { - throw new NullPointerException("Invalid window='" + param + "'"); + throw new IllegalArgumentException("Missing top n value " + + "(number of series to return)"); } - int k = Integer.parseInt(param.trim()); + int topn = 0; + if (param.matches("^[0-9]+$")) { + try { + topn = Integer.parseInt(param); + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException( + "Invalid parameter, must be an integer", nfe); + } + } else { + throw new IllegalArgumentException("Unparseable top n value: " + param); + } + if (topn < 1) { + throw new IllegalArgumentException("Top n value must be greater " + + "than zero: " + topn); + } - int size = 0; - for (DataPoints[] results: queryResults) { - size = size + results.length; + int num_results = 0; + for (DataPoints[] results: query_results) { + num_results += results.length; } - PostAggregatedDataPoints[] seekablePoints = new PostAggregatedDataPoints[size]; - int ix=0; - for (DataPoints[] results: queryResults) { - for (DataPoints dpoints: results) { - List mutablePoints = new ArrayList(); - for (DataPoint point: dpoints) { - mutablePoints.add(point.isInteger() ? - MutableDataPoint.ofLongValue(point.timestamp(), point.longValue()) - : MutableDataPoint.ofDoubleValue(point.timestamp(), point.doubleValue())); + final PostAggregatedDataPoints[] post_agg_results = + new PostAggregatedDataPoints[num_results]; + int ix = 0; + // one or more sub queries (m=...&m=...&m=...) + for (final DataPoints[] sub_query_result : query_results) { + // group bys (m=sum:foo{host=*}) + for (final DataPoints dps : sub_query_result) { + // TODO(cl) - Avoid iterating and copying if we can help it. We should + // be able to pass the original DataPoints object to the seekable view + // and then iterate through it. + final List mutable_points = new ArrayList(); + for (final DataPoint point : dps) { + mutable_points.add(point.isInteger() ? + MutableDataPoint.ofLongValue(point.timestamp(), point.longValue()) + : MutableDataPoint.ofDoubleValue(point.timestamp(), point.doubleValue())); } - seekablePoints[ix++] = new PostAggregatedDataPoints(dpoints, - mutablePoints.toArray(new DataPoint[mutablePoints.size()])); + post_agg_results[ix++] = new PostAggregatedDataPoints(dps, + mutable_points.toArray(new DataPoint[mutable_points.size()])); } } - - if (k >= size) { - return seekablePoints; - } - - SeekableView[] views = new SeekableView[size]; - for (int i=0; i() { + Arrays.sort(max_by_ts, new Comparator() { @Override - public int compare(Entry o1, Entry o2) { + public int compare(TopNSortingEntry o1, TopNSortingEntry o2) { // we want in descending order return -1 * Double.compare(o1.val, o2.val); } }); - DataPoints[] results = new DataPoints[k]; - for (int i=0; i queryParams, String innerExpression) { - return "highestMax(" + innerExpression + ")"; + public String writeStringField(final List query_params, + final String inner_expression) { + return "highestMax(" + inner_expression + ")"; } - + /** + * Aggregator that stores the overall maximum value for the entire series + */ static class MaxCacheAggregator extends Aggregator { - - private final int size; - private final long[] maxLongs; - private final double[] maxDoubles; - private boolean hasLongs = false; - private boolean hasDoubles = false; - + /** The total number of series in the result set, including sub queries and + * group bys */ + private final int total_series; + /** An array of maximum integers by time series */ + private final long[] max_longs; + /** An array of maximum doubles by time series */ + private final double[] max_doubles; + /** Whether or not any of the series contain integers */ + private boolean has_longs = false; + /** Whether or not any of the series contain doubles */ + private boolean has_doubles = false; + /** Query start time in milliseconds for filtering */ private long start; + /** Query end time in milliseconds for filtering */ private long end; - public MaxCacheAggregator(Interpolation method, String name, int size, - long startTimeInMillis, long endTimeInMillis) { + /** + * An aggregator that keeps track of the maximum values for each time series + * in the result set. + * @param method The interpolation method (not used) + * @param name The name of the aggregator + * @param total_series The total number of series in the result set, + * including sub queries and group bys + * @param start Query start time in milliseconds for filtering + * @param end Query end time in milliseconds for filtering + */ + public MaxCacheAggregator(final Interpolation method, final String name, + final int total_series, final long start, final long end) { super(method, name); - this.size = size; - this.start = startTimeInMillis; - this.end = endTimeInMillis; - - this.maxLongs = new long[size]; - this.maxDoubles = new double[size]; - - for (int i=0; i. +package net.opentsdb.query.expression; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import net.opentsdb.core.DataPoint; +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.SeekableView; +import net.opentsdb.core.SeekableViewsForTest; +import net.opentsdb.core.TSQuery; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +@RunWith(PowerMockRunner.class) +@PowerMockIgnore({"javax.management.*", "javax.xml.*", + "ch.qos.*", "org.slf4j.*", + "com.sum.*", "org.xml.*"}) +@PrepareForTest({ TSQuery.class }) +public class TestHighestMax { + + private static long START_TIME = 1356998400000L; + private static int INTERVAL = 60000; + private static int NUM_POINTS = 5; + private static String METRIC = "sys.cpu"; + + private TSQuery data_query; + private SeekableView view; + private DataPoints dps; + private DataPoints[] group_bys; + private List query_results; + private List params; + private HighestMax func; + + @Before + public void before() throws Exception { + view = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 1, 1); + data_query = mock(TSQuery.class); + when(data_query.startTime()).thenReturn(START_TIME); + when(data_query.endTime()).thenReturn(START_TIME + (INTERVAL * NUM_POINTS)); + + dps = PowerMockito.mock(DataPoints.class); + when(dps.iterator()).thenReturn(view); + when(dps.metricName()).thenReturn(METRIC); + + group_bys = new DataPoints[] { dps }; + + query_results = new ArrayList(1); + query_results.add(group_bys); + + params = new ArrayList(1); + func = new HighestMax(); + } + + @Test + public void evaluateTopN1with2SeriesLong() throws Exception { + params.add("1"); + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + group_bys = new DataPoints[] { dps, dps2 }; + query_results.clear(); + query_results.add(group_bys); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(1, results.length); + assertEquals("sys.mem", results[0].metricName()); + + long ts = START_TIME; + long v = 10; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + } + + @Test + public void evaluateTopN2with2SeriesLong() throws Exception { + params.add("2"); + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + group_bys = new DataPoints[] { dps, dps2 }; + query_results.clear(); + query_results.add(group_bys); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(2, results.length); + assertEquals("sys.mem", results[0].metricName()); + assertEquals(METRIC, results[1].metricName()); + + long ts = START_TIME; + long v = 10; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + + ts = START_TIME; + v = 1; + for (DataPoint dp : results[1]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + } + + @Test + public void evaluateTopN100with2SeriesLong() throws Exception { + params.add("100"); + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + DataPoints[] group_bys2 = new DataPoints[] { dps2 }; + query_results.add(group_bys2); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(2, results.length); + assertEquals("sys.mem", results[0].metricName()); + assertEquals(METRIC, results[1].metricName()); + + long ts = START_TIME; + long v = 10; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + + ts = START_TIME; + v = 1; + for (DataPoint dp : results[1]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + } + + @Test + public void evaluateTopN100with2SubQuerySeriesLong() throws Exception { + params.add("100"); + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + group_bys = new DataPoints[] { dps, dps2 }; + query_results.clear(); + query_results.add(group_bys); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(2, results.length); + assertEquals("sys.mem", results[0].metricName()); + assertEquals(METRIC, results[1].metricName()); + + long ts = START_TIME; + long v = 10; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + + ts = START_TIME; + v = 1; + for (DataPoint dp : results[1]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateNullQuery() throws Exception { + params.add("1"); + func.evaluate(null, query_results, params); + } + + @Test + public void evaluateNullResults() throws Exception { + params.add("1"); + final DataPoints[] results = func.evaluate(data_query, null, params); + assertEquals(0, results.length); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateNullParams() throws Exception { + func.evaluate(data_query, query_results, null); + } + + @Test + public void evaluateEmptyResults() throws Exception { + params.add("1"); + final DataPoints[] results = func.evaluate(data_query, + Collections.emptyList(), params); + assertEquals(0, results.length); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateEmptyParams() throws Exception { + func.evaluate(data_query, query_results, params); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateTopnNull() throws Exception { + params.add(null); + func.evaluate(data_query, query_results, params); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateTopnEmpty() throws Exception { + params.add(""); + func.evaluate(data_query, query_results, params); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateTopnZero() throws Exception { + params.add("0"); + func.evaluate(data_query, query_results, params); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateTopnNotaNumber() throws Exception { + params.add("not a number"); + func.evaluate(data_query, query_results, params); + } + + @Test + public void writeStringField() throws Exception { + params.add("1"); + assertEquals("highestMax(inner_expression)", + func.writeStringField(params, "inner_expression")); + assertEquals("highestMax(null)", func.writeStringField(params, null)); + assertEquals("highestMax()", func.writeStringField(params, "")); + assertEquals("highestMax(inner_expression)", + func.writeStringField(null, "inner_expression")); + } +} From 365cbb162621fd258214d97982d06b22e6ff4e1a Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sat, 31 Oct 2015 21:43:57 -0700 Subject: [PATCH 028/233] Add another override to the SeekableViewsForTest to let it emit values as integers when the values would be whole numbers, otherwise doubles. This is useful for testing the aggregation iterators when we are dealing with series containing both floats and ints. Signed-off-by: Chris Larsen --- test/core/SeekableViewsForTest.java | 75 ++++++++++++++++++++++++++--- 1 file changed, 69 insertions(+), 6 deletions(-) diff --git a/test/core/SeekableViewsForTest.java b/test/core/SeekableViewsForTest.java index e53dc27b15..4b638e5ac6 100644 --- a/test/core/SeekableViewsForTest.java +++ b/test/core/SeekableViewsForTest.java @@ -64,8 +64,32 @@ public static SeekableView generator(final long start_time, final boolean is_integer, final double starting_value, final double increment) { + return generator(start_time, sample_period, num_data_points, + is_integer, starting_value, increment, false); + } + + /** + * Creates a {@link SeekableView} that generates a sequence of data points. + * @param start_time Starting timestamp + * @param sample_period Average sample period of data points + * @param num_data_points Total number of data points to generate + * @param is_integer True to generate a sequence of integer data points. + * @param starting_value The starting data point value. + * @param increment How much to increment the values each iteration. + * @param wholes_as_integer Whether or not to return whole numbers (1.0, 2.0, + * etc) as integers to test for functions that should support both. + * Note: Ignored if is_integer is true. + * @return A {@link SeekableView} object + */ + public static SeekableView generator(final long start_time, + final long sample_period, + final int num_data_points, + final boolean is_integer, + final double starting_value, + final double increment, + final boolean wholes_as_integer) { return new DataPointGenerator(start_time, sample_period, num_data_points, - is_integer, starting_value, increment); + is_integer, starting_value, increment, wholes_as_integer); } /** Iterates an array of data points. */ @@ -115,19 +139,28 @@ private static class DataPointGenerator implements SeekableView { private final double increment; private final MutableDataPoint current_data = new MutableDataPoint(); private final MutableDataPoint next_data = new MutableDataPoint(); + private final boolean wholes_as_integer; private int dps_emitted = 0; DataPointGenerator(final long start_time_ms, final long sample_period_ms, final int num_data_points, final boolean is_integer, - final double starting_value, final double increment) { + final double starting_value, final double increment, + final boolean wholes_as_integer) { this.sample_period_ms = sample_period_ms; this.num_data_points = num_data_points; this.is_integer = is_integer; this.increment = increment; + this.wholes_as_integer = wholes_as_integer; if (is_integer) { next_data.reset(start_time_ms, (long)starting_value); } else { - next_data.reset(start_time_ms, starting_value); + if (wholes_as_integer && + (starting_value == Math.floor(starting_value)) && + !Double.isInfinite(starting_value)) { + next_data.reset(start_time_ms, (long)starting_value); + } else { + next_data.reset(start_time_ms, starting_value); + } } } @@ -163,13 +196,20 @@ private void advance() { next_data.reset(next_data.timestamp() + sample_period_ms, next_data.longValue() + (long)increment); } else { - next_data.reset(next_data.timestamp() + sample_period_ms, - next_data.doubleValue() + increment); + final double next = next_data.toDouble() + increment; + if (wholes_as_integer && + (next == Math.floor(next)) && !Double.isInfinite(next)) { + next_data.reset(next_data.timestamp() + sample_period_ms, (long)next); + } else { + next_data.reset(next_data.timestamp() + sample_period_ms, next); + } } dps_emitted++; } + + } - + @Test public void testDataPointGenerator() { SeekableView dpg = generator(100000, 10000, 5, true); @@ -264,4 +304,27 @@ public void testDataPointGenerator_seekToSecond() { } assertFalse(dpg.hasNext()); } + + @Test + public void testDataPointGeneratorWholes() { + SeekableView dpg = generator(100000, 10000, 5, false, 0, 1.5, true); + DataPoint[] expected_data_points = new DataPoint[] { + MutableDataPoint.ofLongValue(100000, 0), + MutableDataPoint.ofDoubleValue(110000, 1.5), + MutableDataPoint.ofLongValue(120000, 3), + MutableDataPoint.ofDoubleValue(130000, 4.5), + MutableDataPoint.ofLongValue(140000, 6), + }; + for (DataPoint expected: expected_data_points) { + assertTrue(dpg.hasNext()); + DataPoint dp = dpg.next(); + assertEquals(expected.timestamp(), dp.timestamp()); + if (expected.isInteger()) { + assertEquals(expected.longValue(), dp.longValue()); + } else { + assertEquals(expected.doubleValue(), dp.doubleValue(), 0.001); + } + } + assertFalse(dpg.hasNext()); + } } From ff60a5b58a2ede4d74b4c1ee920ecb6ae874b02d Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sat, 31 Oct 2015 22:21:22 -0700 Subject: [PATCH 029/233] Change the HighestMax.TopNSortingEntry class to implement a comparator so that it can be re-used a little more. Also add doubles and mixed int/double unit tests to HighestMax Signed-off-by: Chris Larsen --- src/query/expression/HighestMax.java | 20 +++--- test/query/expression/TestHighestMax.java | 75 +++++++++++++++++++++++ 2 files changed, 86 insertions(+), 9 deletions(-) diff --git a/src/query/expression/HighestMax.java b/src/query/expression/HighestMax.java index 5ba67cbcd9..73029b23f4 100644 --- a/src/query/expression/HighestMax.java +++ b/src/query/expression/HighestMax.java @@ -136,13 +136,7 @@ public DataPoints[] evaluate(final TSQuery data_query, } } - Arrays.sort(max_by_ts, new Comparator() { - @Override - public int compare(TopNSortingEntry o1, TopNSortingEntry o2) { - // we want in descending order - return -1 * Double.compare(o1.val, o2.val); - } - }); + Arrays.sort(max_by_ts); final int result_count = Math.min(topn, num_results); final DataPoints[] results = new DataPoints[result_count]; @@ -154,19 +148,26 @@ public int compare(TopNSortingEntry o1, TopNSortingEntry o2) { } /** - * Helper class for sorting the series + * Helper class for sorting the series. It will sort from highest to lowest. */ - static class TopNSortingEntry { + static class TopNSortingEntry implements Comparable { final double val; final int pos; + public TopNSortingEntry(final double val, final int pos) { this.val = val; this.pos = pos; } + @Override public String toString() { return "{" + val + "," + pos + "}"; } + + @Override + public int compareTo(final TopNSortingEntry o) { + return -1 * Double.compare(val, o.val); + } } @Override @@ -264,6 +265,7 @@ public double runDouble(Doubles values) { doubles[ix++] = values.nextDoubleValue(); } for (int i = 0; i < total_series;i++) { + // TODO(cl) - Properly handle NaNs here max_doubles[i] = Math.max(max_doubles[i], doubles[i]); } diff --git a/test/query/expression/TestHighestMax.java b/test/query/expression/TestHighestMax.java index 52f1e405b6..2426204bc5 100644 --- a/test/query/expression/TestHighestMax.java +++ b/test/query/expression/TestHighestMax.java @@ -13,6 +13,7 @@ package net.opentsdb.query.expression; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -220,6 +221,80 @@ public void evaluateTopN100with2SubQuerySeriesLong() throws Exception { } } + @Test + public void evaluateTopN2with2SeriesDouble() throws Exception { + params.add("2"); + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, false, 10, 1.5); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + group_bys = new DataPoints[] { dps, dps2 }; + query_results.clear(); + query_results.add(group_bys); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(2, results.length); + assertEquals("sys.mem", results[0].metricName()); + assertEquals(METRIC, results[1].metricName()); + + long ts = START_TIME; + double v = 10; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertFalse(dp.isInteger()); + assertEquals(v, dp.doubleValue(), 0.001); + ts += INTERVAL; + v += 1.5; + } + + ts = START_TIME; + v = 1; + for (DataPoint dp : results[1]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.toDouble(), 0.001); + ts += INTERVAL; + v += 1; + } + } + + @Test + public void evaluateTopN1with2SeriesLongDoubleMixed() throws Exception { + params.add("1"); + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, false, 10, 1.5, true); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + group_bys = new DataPoints[] { dps, dps2 }; + query_results.clear(); + query_results.add(group_bys); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(1, results.length); + assertEquals("sys.mem", results[0].metricName()); + + long ts = START_TIME; + double v = 10; + boolean toggle = true; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + if (toggle) { + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue(), 0.001); + } else { + assertFalse(dp.isInteger()); + assertEquals(v, dp.doubleValue(), 0.001); + } + toggle = !toggle; + ts += INTERVAL; + v += 1.5; + } + } + @Test (expected = IllegalArgumentException.class) public void evaluateNullQuery() throws Exception { params.add("1"); From 9015ad3b70abecfaa54f24d9d98719467e07b20a Mon Sep 17 00:00:00 2001 From: Arjun Satish Date: Sat, 31 Oct 2015 21:07:00 -0700 Subject: [PATCH 030/233] Add the HighestCurrent expression function to return the top n series with the maximum current value, sorted by value. Signed-off-by: Chris Larsen --- src/query/expression/HighestCurrent.java | 240 +++++++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100644 src/query/expression/HighestCurrent.java diff --git a/src/query/expression/HighestCurrent.java b/src/query/expression/HighestCurrent.java new file mode 100644 index 0000000000..aa19df885f --- /dev/null +++ b/src/query/expression/HighestCurrent.java @@ -0,0 +1,240 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +import net.opentsdb.core.AggregationIterator; +import net.opentsdb.core.Aggregator; +import net.opentsdb.core.Aggregators; +import net.opentsdb.core.DataPoint; +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.MutableDataPoint; +import net.opentsdb.core.SeekableView; +import net.opentsdb.core.TSQuery; +import net.opentsdb.core.Aggregators.Interpolation; + +public class HighestCurrent implements Expression { + + @Override + public DataPoints[] evaluate(TSQuery data_query, List queryResults, + List params) { + if (queryResults == null || queryResults.isEmpty()) { + throw new NullPointerException("Query results cannot be empty"); + } + + if (params == null || params.isEmpty()) { + throw new NullPointerException("Need aggregation window for moving average"); + } + + String param = params.get(0); + if (param == null || param.length() == 0) { + throw new NullPointerException("Invalid window='" + param + "'"); + } + + int k = Integer.parseInt(param.trim()); + + int size = 0; + for (DataPoints[] results: queryResults) { + size = size + results.length; + } + + PostAggregatedDataPoints[] seekablePoints = new PostAggregatedDataPoints[size]; + int ix=0; + for (DataPoints[] results: queryResults) { + for (DataPoints dpoints: results) { + List mutablePoints = new ArrayList(); + for (DataPoint point: dpoints) { + mutablePoints.add(point.isInteger() ? + MutableDataPoint.ofLongValue(point.timestamp(), point.longValue()) + : MutableDataPoint.ofDoubleValue(point.timestamp(), point.doubleValue())); + } + seekablePoints[ix++] = new PostAggregatedDataPoints(dpoints, + mutablePoints.toArray(new DataPoint[mutablePoints.size()])); + } + } + + if (k >= size) { + return seekablePoints; + } + + SeekableView[] views = new SeekableView[size]; + for (int i=0; i() { + @Override + public int compare(Entry o1, Entry o2) { + return -1 * Double.compare(o1.val, o2.val); + } + }); + + DataPoints[] results = new DataPoints[k]; + for (int i=0; i queryParams, String innerExpression) { + return "highestCurrent(" + innerExpression + ")"; + } + + + public static class MaxLatestAggregator extends Aggregator { + private final int size; + private final long[] maxLongs; + private final double[] maxDoubles; + private final long start; + private final long end; + private boolean hasLongs = false; + private boolean hasDoubles = false; + private long latestTS = -1; + + public MaxLatestAggregator(Interpolation method, String name, int size, + long startTimeInMillis, long endTimeInMillis) { + super(method, name); + this.size = size; + this.start = startTimeInMillis; + this.end = endTimeInMillis; + + + this.maxLongs = new long[size]; + this.maxDoubles = new double[size]; + + for (int i=0; i end) { + return 0; + } + } + + long[] longs = new long[size]; + int ix = 0; + longs[ix++] = values.nextLongValue(); + while (values.hasNextValue()) { + longs[ix++] = values.nextLongValue(); + } + + if (values instanceof DataPoint) { + long ts = ((DataPoint) values).timestamp(); + if (ts > latestTS) { + System.arraycopy(longs, 0, maxLongs, 0, size); + } + } + + hasLongs = true; + return 0; + } + + @Override + public double runDouble(Doubles values) { + if (values instanceof DataPoint) { + long ts = ((DataPoint) values).timestamp(); + //data point falls outside required range + if (ts < start || ts > end) { + return 0; + } + } + + double[] doubles = new double[size]; + int ix = 0; + doubles[ix++] = values.nextDoubleValue(); + while (values.hasNextValue()) { + doubles[ix++] = values.nextDoubleValue(); + } + + if (values instanceof DataPoint) { + long ts = ((DataPoint) values).timestamp(); + if (ts > latestTS) { + System.arraycopy(doubles, 0, maxDoubles, 0, size); + } + } + + hasDoubles = true; + return 0; + } + + public long[] getLongMaxes() { + return maxLongs; + } + + public double[] getDoubleMaxes() { + return maxDoubles; + } + + public boolean hasLongs() { + return hasLongs; + } + + public boolean hasDoubles() { + return hasDoubles; + } + + } +} From 9b0d86baf448b1a81a318d347b8d8c0f0f4586ab Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sat, 31 Oct 2015 22:22:47 -0700 Subject: [PATCH 031/233] Cleanup the HighestCurrent class with code formatting and comments. Add a unit test for HighestCurrent. Signed-off-by: Chris Larsen --- Makefile.am | 2 + src/query/expression/HighestCurrent.java | 249 ++++++----- test/query/expression/TestHighestCurrent.java | 393 ++++++++++++++++++ 3 files changed, 539 insertions(+), 105 deletions(-) create mode 100644 test/query/expression/TestHighestCurrent.java diff --git a/Makefile.am b/Makefile.am index df8051b7f2..05cd94b15a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -74,6 +74,7 @@ tsdb_SRC := \ src/meta/TSUIDQuery.java \ src/meta/UIDMeta.java \ src/query/QueryUtil.java \ + src/query/expression/HighestCurrent.java \ src/query/expression/HighestMax.java \ src/query/expression/MovingAverage.java \ src/query/expression/PostAggregatedDataPoints.java \ @@ -221,6 +222,7 @@ test_SRC := \ test/meta/TestTSMeta.java \ test/meta/TestTSUIDQuery.java \ test/meta/TestUIDMeta.java \ + test/query/expression/TestHighestCurrent.java \ test/query/expression/TestHighestMax.java \ test/query/expression/TestMovingAverage.java \ test/query/expression/TestPostAggregatedDataPoints.java \ diff --git a/src/query/expression/HighestCurrent.java b/src/query/expression/HighestCurrent.java index aa19df885f..11d9f04f82 100644 --- a/src/query/expression/HighestCurrent.java +++ b/src/query/expression/HighestCurrent.java @@ -14,7 +14,6 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.Comparator; import java.util.List; import net.opentsdb.core.AggregationIterator; @@ -26,146 +25,184 @@ import net.opentsdb.core.SeekableView; import net.opentsdb.core.TSQuery; import net.opentsdb.core.Aggregators.Interpolation; - +import net.opentsdb.query.expression.HighestMax.TopNSortingEntry; + +/** + * Implements top-n functionality by iterating over each of the time series, + * sorting and returning the top "n" time series with the highest current (or + * latest) value. + * @since 2.3 + */ public class HighestCurrent implements Expression { @Override - public DataPoints[] evaluate(TSQuery data_query, List queryResults, - List params) { - if (queryResults == null || queryResults.isEmpty()) { - throw new NullPointerException("Query results cannot be empty"); + public DataPoints[] evaluate(final TSQuery data_query, + final List query_results, final List params) { + if (data_query == null) { + throw new IllegalArgumentException("Missing time series query"); } - + if (query_results == null || query_results.isEmpty()) { + return new DataPoints[]{}; + } + // TODO(cl) - allow for empty top-n maybe? Just sort the results by max? if (params == null || params.isEmpty()) { - throw new NullPointerException("Need aggregation window for moving average"); + throw new IllegalArgumentException("Need aggregation window for moving average"); } String param = params.get(0); if (param == null || param.length() == 0) { - throw new NullPointerException("Invalid window='" + param + "'"); + throw new IllegalArgumentException("Missing top n value " + + "(number of series to return)"); } - int k = Integer.parseInt(param.trim()); + int topn = 0; + if (param.matches("^[0-9]+$")) { + try { + topn = Integer.parseInt(param); + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException( + "Invalid parameter, must be an integer", nfe); + } + } else { + throw new IllegalArgumentException("Unparseable top n value: " + param); + } + if (topn < 1) { + throw new IllegalArgumentException("Top n value must be greater " + + "than zero: " + topn); + } - int size = 0; - for (DataPoints[] results: queryResults) { - size = size + results.length; + int num_results = 0; + for (DataPoints[] results: query_results) { + num_results += results.length; } - PostAggregatedDataPoints[] seekablePoints = new PostAggregatedDataPoints[size]; - int ix=0; - for (DataPoints[] results: queryResults) { - for (DataPoints dpoints: results) { - List mutablePoints = new ArrayList(); - for (DataPoint point: dpoints) { - mutablePoints.add(point.isInteger() ? - MutableDataPoint.ofLongValue(point.timestamp(), point.longValue()) - : MutableDataPoint.ofDoubleValue(point.timestamp(), point.doubleValue())); + final PostAggregatedDataPoints[] post_agg_results = + new PostAggregatedDataPoints[num_results]; + int ix = 0; + // one or more sub queries (m=...&m=...&m=...) + for (final DataPoints[] sub_query_result : query_results) { + // group bys (m=sum:foo{host=*}) + for (final DataPoints dps : sub_query_result) { + // TODO(cl) - Avoid iterating and copying if we can help it. We should + // be able to pass the original DataPoints object to the seekable view + // and then iterate through it. + final List mutable_points = new ArrayList(); + for (final DataPoint point : dps) { + mutable_points.add(point.isInteger() ? + MutableDataPoint.ofLongValue(point.timestamp(), point.longValue()) + : MutableDataPoint.ofDoubleValue(point.timestamp(), point.doubleValue())); } - seekablePoints[ix++] = new PostAggregatedDataPoints(dpoints, - mutablePoints.toArray(new DataPoint[mutablePoints.size()])); + post_agg_results[ix++] = new PostAggregatedDataPoints(dps, + mutable_points.toArray(new DataPoint[mutable_points.size()])); } } - - if (k >= size) { - return seekablePoints; - } - - SeekableView[] views = new SeekableView[size]; - for (int i=0; i() { - @Override - public int compare(Entry o1, Entry o2) { - return -1 * Double.compare(o1.val, o2.val); - } - }); + Arrays.sort(max_by_ts); - DataPoints[] results = new DataPoints[k]; - for (int i=0; i queryParams, String innerExpression) { - return "highestCurrent(" + innerExpression + ")"; + public String writeStringField(final List query_params, + final String inner_expression) { + return "highestCurrent(" + inner_expression + ")"; } - + /** + * Aggregator that stores only the latest value for each series so that they + * can be sorted on it + */ public static class MaxLatestAggregator extends Aggregator { - private final int size; - private final long[] maxLongs; - private final double[] maxDoubles; - private final long start; - private final long end; - private boolean hasLongs = false; - private boolean hasDoubles = false; - private long latestTS = -1; - - public MaxLatestAggregator(Interpolation method, String name, int size, - long startTimeInMillis, long endTimeInMillis) { + /** The total number of series in the result set, including sub queries and + * group bys */ + private final int total_series; + /** An array of maximum integers by time series */ + private final long[] max_longs; + /** An array of maximum doubles by time series */ + private final double[] max_doubles; + /** Whether or not any of the series contain integers */ + private boolean has_longs = false; + /** Whether or not any of the series contain doubles */ + private boolean has_doubles = false; + /** Query start time in milliseconds for filtering */ + private long start; + /** Query end time in milliseconds for filtering */ + private long end; + /** The most recent timestamp in the different series */ + private long latest_ts = -1; + + /** + * An aggregator that keeps track of the maximum latest value for each series + * @param method The interpolation method (not used) + * @param name The name of the aggregator + * @param total_series The total number of series in the result set, + * including sub queries and group bys + * @param start Query start time in milliseconds for filtering + * @param end Query end time in milliseconds for filtering + */ + public MaxLatestAggregator(final Interpolation method, final String name, + final int total_series, final long start, final long end) { super(method, name); - this.size = size; - this.start = startTimeInMillis; - this.end = endTimeInMillis; - - - this.maxLongs = new long[size]; - this.maxDoubles = new double[size]; - - for (int i=0; i latestTS) { - System.arraycopy(longs, 0, maxLongs, 0, size); + final long ts = ((DataPoint) values).timestamp(); + if (ts > latest_ts) { + System.arraycopy(longs, 0, max_longs, 0, total_series); } } - hasLongs = true; + has_longs = true; return 0; } @Override public double runDouble(Doubles values) { + // TODO(cl) - Can we get anything other than a DataPoint? if (values instanceof DataPoint) { long ts = ((DataPoint) values).timestamp(); //data point falls outside required range @@ -202,7 +240,8 @@ public double runDouble(Doubles values) { } } - double[] doubles = new double[size]; + // TODO(cl) - Properly handle NaNs here + final double[] doubles = new double[total_series]; int ix = 0; doubles[ix++] = values.nextDoubleValue(); while (values.hasNextValue()) { @@ -210,30 +249,30 @@ public double runDouble(Doubles values) { } if (values instanceof DataPoint) { - long ts = ((DataPoint) values).timestamp(); - if (ts > latestTS) { - System.arraycopy(doubles, 0, maxDoubles, 0, size); + final long ts = ((DataPoint) values).timestamp(); + if (ts > latest_ts) { + System.arraycopy(doubles, 0, max_doubles, 0, total_series); } } - hasDoubles = true; + has_doubles = true; return 0; } public long[] getLongMaxes() { - return maxLongs; + return max_longs; } public double[] getDoubleMaxes() { - return maxDoubles; + return max_doubles; } public boolean hasLongs() { - return hasLongs; + return has_longs; } public boolean hasDoubles() { - return hasDoubles; + return has_doubles; } } diff --git a/test/query/expression/TestHighestCurrent.java b/test/query/expression/TestHighestCurrent.java new file mode 100644 index 0000000000..145c81f41d --- /dev/null +++ b/test/query/expression/TestHighestCurrent.java @@ -0,0 +1,393 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import net.opentsdb.core.DataPoint; +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.SeekableView; +import net.opentsdb.core.SeekableViewsForTest; +import net.opentsdb.core.TSQuery; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +@RunWith(PowerMockRunner.class) +@PowerMockIgnore({"javax.management.*", "javax.xml.*", + "ch.qos.*", "org.slf4j.*", + "com.sum.*", "org.xml.*"}) +@PrepareForTest({ TSQuery.class }) +public class TestHighestCurrent { + + private static long START_TIME = 1356998400000L; + private static int INTERVAL = 60000; + private static int NUM_POINTS = 5; + private static String METRIC = "sys.cpu"; + + private TSQuery data_query; + private SeekableView view; + private DataPoints dps; + private DataPoints[] group_bys; + private List query_results; + private List params; + private HighestCurrent func; + + @Before + public void before() throws Exception { + view = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 1, 1); + data_query = mock(TSQuery.class); + when(data_query.startTime()).thenReturn(START_TIME); + when(data_query.endTime()).thenReturn(START_TIME + (INTERVAL * NUM_POINTS)); + + dps = PowerMockito.mock(DataPoints.class); + when(dps.iterator()).thenReturn(view); + when(dps.metricName()).thenReturn(METRIC); + + group_bys = new DataPoints[] { dps }; + + query_results = new ArrayList(1); + query_results.add(group_bys); + + params = new ArrayList(1); + func = new HighestCurrent(); + } + + @Test + public void evaluateTopN1with2SeriesLong() throws Exception { + params.add("1"); + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + group_bys = new DataPoints[] { dps, dps2 }; + query_results.clear(); + query_results.add(group_bys); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(1, results.length); + assertEquals("sys.mem", results[0].metricName()); + + long ts = START_TIME; + long v = 10; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + } + + @Test + public void evaluateTopN2with2SeriesLong() throws Exception { + params.add("2"); + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + group_bys = new DataPoints[] { dps, dps2 }; + query_results.clear(); + query_results.add(group_bys); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(2, results.length); + assertEquals("sys.mem", results[0].metricName()); + assertEquals(METRIC, results[1].metricName()); + + long ts = START_TIME; + long v = 10; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + + ts = START_TIME; + v = 1; + for (DataPoint dp : results[1]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + } + + @Test + public void evaluateTopN100with2SeriesLong() throws Exception { + params.add("100"); + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + DataPoints[] group_bys2 = new DataPoints[] { dps2 }; + query_results.add(group_bys2); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(2, results.length); + assertEquals("sys.mem", results[0].metricName()); + assertEquals(METRIC, results[1].metricName()); + + long ts = START_TIME; + long v = 10; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + + ts = START_TIME; + v = 1; + for (DataPoint dp : results[1]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + } + + @Test + public void evaluateTopN100with2SubQuerySeriesLong() throws Exception { + params.add("100"); + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + group_bys = new DataPoints[] { dps, dps2 }; + query_results.clear(); + query_results.add(group_bys); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(2, results.length); + assertEquals("sys.mem", results[0].metricName()); + assertEquals(METRIC, results[1].metricName()); + + long ts = START_TIME; + long v = 10; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + + ts = START_TIME; + v = 1; + for (DataPoint dp : results[1]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + } + + @Test + public void evaluateTopN2with2SeriesDouble() throws Exception { + params.add("2"); + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, false, 10, 1.5); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + group_bys = new DataPoints[] { dps, dps2 }; + query_results.clear(); + query_results.add(group_bys); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(2, results.length); + assertEquals("sys.mem", results[0].metricName()); + assertEquals(METRIC, results[1].metricName()); + + long ts = START_TIME; + double v = 10; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertFalse(dp.isInteger()); + assertEquals(v, dp.doubleValue(), 0.001); + ts += INTERVAL; + v += 1.5; + } + + ts = START_TIME; + v = 1; + for (DataPoint dp : results[1]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.toDouble(), 0.001); + ts += INTERVAL; + v += 1; + } + } + + @Test + public void evaluateTopN1with2SeriesLongDoubleMixed() throws Exception { + params.add("1"); + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, false, 10, 1.5, true); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + group_bys = new DataPoints[] { dps, dps2 }; + query_results.clear(); + query_results.add(group_bys); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(1, results.length); + assertEquals("sys.mem", results[0].metricName()); + + long ts = START_TIME; + double v = 10; + boolean toggle = true; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + if (toggle) { + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue(), 0.001); + } else { + assertFalse(dp.isInteger()); + assertEquals(v, dp.doubleValue(), 0.001); + } + toggle = !toggle; + ts += INTERVAL; + v += 1.5; + } + } + + @Test + public void evaluateTopN1with2SeriesDiffSpan() throws Exception { + params.add("1"); + // in this case one series ends earlier than the other so it's removed + // even though it's last value was greater than the winners. + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + 3, true, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + group_bys = new DataPoints[] { dps, dps2 }; + query_results.clear(); + query_results.add(group_bys); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(1, results.length); + assertEquals(METRIC, results[0].metricName()); + + long ts = START_TIME; + long v = 1; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateNullQuery() throws Exception { + params.add("1"); + func.evaluate(null, query_results, params); + } + + @Test + public void evaluateNullResults() throws Exception { + params.add("1"); + final DataPoints[] results = func.evaluate(data_query, null, params); + assertEquals(0, results.length); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateNullParams() throws Exception { + func.evaluate(data_query, query_results, null); + } + + @Test + public void evaluateEmptyResults() throws Exception { + params.add("1"); + final DataPoints[] results = func.evaluate(data_query, + Collections.emptyList(), params); + assertEquals(0, results.length); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateEmptyParams() throws Exception { + func.evaluate(data_query, query_results, params); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateTopnNull() throws Exception { + params.add(null); + func.evaluate(data_query, query_results, params); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateTopnEmpty() throws Exception { + params.add(""); + func.evaluate(data_query, query_results, params); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateTopnZero() throws Exception { + params.add("0"); + func.evaluate(data_query, query_results, params); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateTopnNotaNumber() throws Exception { + params.add("not a number"); + func.evaluate(data_query, query_results, params); + } + + @Test + public void writeStringField() throws Exception { + params.add("1"); + assertEquals("highestCurrent(inner_expression)", + func.writeStringField(params, "inner_expression")); + assertEquals("highestCurrent(null)", func.writeStringField(params, null)); + assertEquals("highestCurrent()", func.writeStringField(params, "")); + assertEquals("highestCurrent(inner_expression)", + func.writeStringField(null, "inner_expression")); + } +} From a8237109c9551b5c13ec1ebd80ba34b90d539db3 Mon Sep 17 00:00:00 2001 From: Arjun Satish Date: Sun, 1 Nov 2015 09:08:28 -0800 Subject: [PATCH 032/233] Add the Scale function for multiplying each series by some factor. Signed-off-by: Chris Larsen --- src/query/expression/Scale.java | 72 +++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 src/query/expression/Scale.java diff --git a/src/query/expression/Scale.java b/src/query/expression/Scale.java new file mode 100644 index 0000000000..ee7ecd6420 --- /dev/null +++ b/src/query/expression/Scale.java @@ -0,0 +1,72 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import java.util.List; + +import net.opentsdb.core.DataPoint; +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.MutableDataPoint; +import net.opentsdb.core.SeekableView; +import net.opentsdb.core.TSQuery; + +public class Scale implements Expression { + @Override + public DataPoints[] evaluate(TSQuery data_query, List queryResults, List params) { + if (queryResults == null || queryResults.isEmpty()) { + throw new NullPointerException("Query results cannot be empty"); + } + + if (params == null || params.isEmpty()) { + throw new NullPointerException("Scaling parameter not available"); + } + + String factor = params.get(0); + factor = factor.replaceAll("'|\"", "").trim(); + double scaleFactor = Double.parseDouble(factor); + + DataPoints[] inputPoints = queryResults.get(0); + DataPoints[] outputPoints = new DataPoints[inputPoints.length]; + + for (int i=0; i queryParams, String innerExpression) { + return "scale(" + innerExpression + ")"; + } + +} From 4b7ea0f11fa5490934fafbbd9f774ef7fcf8c441 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sun, 1 Nov 2015 09:39:55 -0800 Subject: [PATCH 033/233] Fix up the Scale expression: - Apply scale to all sub queries instead of only the first one - Fix the .size() issue in the data points array sizing - Cleanup, formatting and unit tests Signed-off-by: Chris Larsen --- Makefile.am | 2 + src/query/expression/Scale.java | 100 ++++--- test/query/expression/TestScale.java | 402 +++++++++++++++++++++++++++ 3 files changed, 473 insertions(+), 31 deletions(-) create mode 100644 test/query/expression/TestScale.java diff --git a/Makefile.am b/Makefile.am index 05cd94b15a..f04c683ddd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -78,6 +78,7 @@ tsdb_SRC := \ src/query/expression/HighestMax.java \ src/query/expression/MovingAverage.java \ src/query/expression/PostAggregatedDataPoints.java \ + src/query/expression/Scale.java \ src/query/filter/TagVFilter.java \ src/query/filter/TagVLiteralOrFilter.java \ src/query/filter/TagVNotKeyFilter.java \ @@ -226,6 +227,7 @@ test_SRC := \ test/query/expression/TestHighestMax.java \ test/query/expression/TestMovingAverage.java \ test/query/expression/TestPostAggregatedDataPoints.java \ + test/query/expression/TestScale.java \ test/query/filter/TestTagVFilter.java \ test/query/filter/TestTagVLiteralOrFilter.java \ test/query/filter/TestTagVNotKeyFilter.java \ diff --git a/src/query/expression/Scale.java b/src/query/expression/Scale.java index ee7ecd6420..2a7bf3649f 100644 --- a/src/query/expression/Scale.java +++ b/src/query/expression/Scale.java @@ -12,6 +12,7 @@ // see . package net.opentsdb.query.expression; +import java.util.ArrayList; import java.util.List; import net.opentsdb.core.DataPoint; @@ -20,53 +21,90 @@ import net.opentsdb.core.SeekableView; import net.opentsdb.core.TSQuery; +/** + * Multiplies each data point in the series by the given factor. + * @since 2.3 + */ public class Scale implements Expression { + @Override - public DataPoints[] evaluate(TSQuery data_query, List queryResults, List params) { - if (queryResults == null || queryResults.isEmpty()) { - throw new NullPointerException("Query results cannot be empty"); + public DataPoints[] evaluate(final TSQuery data_query, + final List query_results, final List params) { + if (data_query == null) { + throw new IllegalArgumentException("Missing time series query"); + } + if (query_results == null || query_results.isEmpty()) { + return new DataPoints[]{}; } - if (params == null || params.isEmpty()) { - throw new NullPointerException("Scaling parameter not available"); + throw new IllegalArgumentException("Missing scaling factor"); } - String factor = params.get(0); - factor = factor.replaceAll("'|\"", "").trim(); - double scaleFactor = Double.parseDouble(factor); - - DataPoints[] inputPoints = queryResults.get(0); - DataPoints[] outputPoints = new DataPoints[inputPoints.length]; - - for (int i=0; i dps = new ArrayList(); + final boolean scale_is_int = (scale_factor == Math.floor(scale_factor)) && + !Double.isInfinite(scale_factor); + final SeekableView view = points.iterator(); while (view.hasNext()) { DataPoint pt = view.next(); - if (pt.isInteger()) { - dps[i] = MutableDataPoint.ofDoubleValue(pt.timestamp(), scaleFactor * pt.longValue()); + if (pt.isInteger() && scale_is_int) { + dps.add(MutableDataPoint.ofLongValue(pt.timestamp(), + (long)scale_factor * pt.longValue())); } else { - dps[i] = MutableDataPoint.ofDoubleValue(pt.timestamp(), scaleFactor * pt.doubleValue()); + // NaNs are fine here, they'll just be re-computed as NaN + dps.add(MutableDataPoint.ofDoubleValue(pt.timestamp(), + scale_factor * pt.toDouble())); } - i++; } - - return new PostAggregatedDataPoints(points, dps); + final DataPoint[] results = new DataPoint[dps.size()]; + dps.toArray(results); + return new PostAggregatedDataPoints(points, results); } @Override - public String writeStringField(List queryParams, String innerExpression) { - return "scale(" + innerExpression + ")"; + public String writeStringField(final List query_params, + final String inner_expression) { + return "scale(" + inner_expression + ")"; } } diff --git a/test/query/expression/TestScale.java b/test/query/expression/TestScale.java new file mode 100644 index 0000000000..f7f35dd687 --- /dev/null +++ b/test/query/expression/TestScale.java @@ -0,0 +1,402 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import net.opentsdb.core.DataPoint; +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.SeekableView; +import net.opentsdb.core.SeekableViewsForTest; +import net.opentsdb.core.TSQuery; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +@RunWith(PowerMockRunner.class) +@PowerMockIgnore({"javax.management.*", "javax.xml.*", + "ch.qos.*", "org.slf4j.*", + "com.sum.*", "org.xml.*"}) +@PrepareForTest({ TSQuery.class }) +public class TestScale { + + private static long START_TIME = 1356998400000L; + private static int INTERVAL = 60000; + private static int NUM_POINTS = 5; + private static String METRIC = "sys.cpu"; + + private TSQuery data_query; + private SeekableView view; + private DataPoints dps; + private DataPoints[] group_bys; + private List query_results; + private List params; + private Scale func; + + @Before + public void before() throws Exception { + view = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 1, 1); + data_query = mock(TSQuery.class); + when(data_query.startTime()).thenReturn(START_TIME); + when(data_query.endTime()).thenReturn(START_TIME + (INTERVAL * NUM_POINTS)); + + dps = PowerMockito.mock(DataPoints.class); + when(dps.iterator()).thenReturn(view); + when(dps.metricName()).thenReturn(METRIC); + + group_bys = new DataPoints[] { dps }; + + query_results = new ArrayList(1); + query_results.add(group_bys); + + params = new ArrayList(1); + func = new Scale(); + } + + @Test + public void evaluateFactor1GroupByLong() throws Exception { + params.add("1"); + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + group_bys = new DataPoints[] { dps, dps2 }; + query_results.clear(); + query_results.add(group_bys); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(2, results.length); + assertEquals(METRIC, results[0].metricName()); + assertEquals("sys.mem", results[1].metricName()); + + long ts = START_TIME; + long v = 1; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + ts = START_TIME; + v = 10; + for (DataPoint dp : results[1]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + } + + @Test + public void evaluateFactor1GroupByDouble() throws Exception { + params.add("1"); + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, false, 10, 1.5); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + group_bys = new DataPoints[] { dps, dps2 }; + query_results.clear(); + query_results.add(group_bys); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(2, results.length); + assertEquals(METRIC, results[0].metricName()); + assertEquals("sys.mem", results[1].metricName()); + + long ts = START_TIME; + double v = 1; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals((long)v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + ts = START_TIME; + v = 10; + for (DataPoint dp : results[1]) { + assertEquals(ts, dp.timestamp()); + assertFalse(dp.isInteger()); + assertEquals(v, dp.doubleValue(), 0.001); + ts += INTERVAL; + v += 1.5; + } + } + + @Test + public void evaluateFactor1point5GroupBy() throws Exception { + params.add("1.5"); + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + group_bys = new DataPoints[] { dps, dps2 }; + query_results.clear(); + query_results.add(group_bys); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(2, results.length); + assertEquals(METRIC, results[0].metricName()); + assertEquals("sys.mem", results[1].metricName()); + + long ts = START_TIME; + double v = 1.5; + for (DataPoint dp : results[0]) { + System.out.println(dp.timestamp() + " : " + dp.toDouble()); + assertEquals(ts, dp.timestamp()); + assertFalse(dp.isInteger()); + assertEquals(v, dp.doubleValue(), 0.001); + ts += INTERVAL; + v += 1.5; + } + ts = START_TIME; + v = 15; + for (DataPoint dp : results[1]) { + assertEquals(ts, dp.timestamp()); + assertFalse(dp.isInteger()); + assertEquals(v, dp.doubleValue(), 0.001); + ts += INTERVAL; + v += 1.5; + } + } + + @Test + public void evaluateFactor1024GroupBy() throws Exception { + params.add("1024"); + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + group_bys = new DataPoints[] { dps, dps2 }; + query_results.clear(); + query_results.add(group_bys); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(2, results.length); + assertEquals(METRIC, results[0].metricName()); + assertEquals("sys.mem", results[1].metricName()); + + long ts = START_TIME; + long v = 1024; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1024; + } + ts = START_TIME; + v = 10240; + for (DataPoint dp : results[1]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1024; + } + } + + @Test + public void evaluateFactor1SubQuerySeries() throws Exception { + params.add("1"); + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + group_bys = new DataPoints[] { dps, dps2 }; + query_results.clear(); + query_results.add(group_bys); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(2, results.length); + assertEquals(METRIC, results[0].metricName()); + assertEquals("sys.mem", results[1].metricName()); + + long ts = START_TIME; + long v = 1; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + ts = START_TIME; + v = 10; + for (DataPoint dp : results[1]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + } + + @Test + public void evaluateFactor0GroupByLong() throws Exception { + params.add("0"); + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + group_bys = new DataPoints[] { dps, dps2 }; + query_results.clear(); + query_results.add(group_bys); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(2, results.length); + assertEquals(METRIC, results[0].metricName()); + assertEquals("sys.mem", results[1].metricName()); + + long ts = START_TIME; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(0, dp.longValue()); + ts += INTERVAL; + } + ts = START_TIME; + for (DataPoint dp : results[1]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(0, dp.longValue()); + ts += INTERVAL; + } + } + + @Test + public void evaluateFactorNegative1GroupByLong() throws Exception { + params.add("-1"); + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + group_bys = new DataPoints[] { dps, dps2 }; + query_results.clear(); + query_results.add(group_bys); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(2, results.length); + assertEquals(METRIC, results[0].metricName()); + assertEquals("sys.mem", results[1].metricName()); + + long ts = START_TIME; + long v = -1; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v -= 1; + } + ts = START_TIME; + v = -10; + for (DataPoint dp : results[1]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v -= 1; + } + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateNullQuery() throws Exception { + params.add("1"); + func.evaluate(null, query_results, params); + } + + @Test + public void evaluateNullResults() throws Exception { + params.add("1"); + final DataPoints[] results = func.evaluate(data_query, null, params); + assertEquals(0, results.length); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateNullParams() throws Exception { + func.evaluate(data_query, query_results, null); + } + + @Test + public void evaluateEmptyResults() throws Exception { + params.add("1"); + final DataPoints[] results = func.evaluate(data_query, + Collections.emptyList(), params); + assertEquals(0, results.length); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateEmptyParams() throws Exception { + func.evaluate(data_query, query_results, params); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateScaleNull() throws Exception { + params.add(null); + func.evaluate(data_query, query_results, params); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateScaleEmpty() throws Exception { + params.add(""); + func.evaluate(data_query, query_results, params); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateScaleNotaNumber() throws Exception { + params.add("not a number"); + func.evaluate(data_query, query_results, params); + } + + @Test + public void writeStringField() throws Exception { + params.add("1"); + assertEquals("scale(inner_expression)", + func.writeStringField(params, "inner_expression")); + assertEquals("scale(null)", func.writeStringField(params, null)); + assertEquals("scale()", func.writeStringField(params, "")); + assertEquals("scale(inner_expression)", + func.writeStringField(null, "inner_expression")); + } +} From 2f45ca75a2b02a84e263435460a56e5009c1e579 Mon Sep 17 00:00:00 2001 From: Arjun Satish Date: Sun, 1 Nov 2015 09:44:27 -0800 Subject: [PATCH 034/233] Add the Absolute expression function for calculating absolute value on all data points in a series. Signed-off-by: Chris Larsen --- src/query/expression/Absolute.java | 65 ++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/query/expression/Absolute.java diff --git a/src/query/expression/Absolute.java b/src/query/expression/Absolute.java new file mode 100644 index 0000000000..3f9e406ecc --- /dev/null +++ b/src/query/expression/Absolute.java @@ -0,0 +1,65 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import java.util.List; + +import net.opentsdb.core.DataPoint; +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.MutableDataPoint; +import net.opentsdb.core.SeekableView; +import net.opentsdb.core.TSQuery; + +public class Absolute implements Expression { + + @Override + public DataPoints[] evaluate(TSQuery data_query, List queryResults, List params) { + if (queryResults == null || queryResults.isEmpty()) { + throw new NullPointerException("Query results cannot be empty"); + } + + DataPoints[] inputPoints = queryResults.get(0); + DataPoints[] outputPoints = new DataPoints[inputPoints.length]; + + for (int i=0; i queryParams, String innerExpression) { + return "absolute(" + innerExpression + ")"; + } + +} From ac70a06ba188343fd6d4c8b4767967c3f1192814 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sun, 1 Nov 2015 09:56:26 -0800 Subject: [PATCH 035/233] Fixup the Absolute expression: - Enable it to handle all sub query results too - Fix the .size() issue in the data points array sizing - Fix the Integer to double conversion that shouldn't have been - Cleanup, formatting and unit tests Signed-off-by: Chris Larsen --- Makefile.am | 2 + src/query/expression/Absolute.java | 66 +++-- test/query/expression/TestAbsolute.java | 307 ++++++++++++++++++++++++ 3 files changed, 354 insertions(+), 21 deletions(-) create mode 100644 test/query/expression/TestAbsolute.java diff --git a/Makefile.am b/Makefile.am index f04c683ddd..de306c4076 100644 --- a/Makefile.am +++ b/Makefile.am @@ -74,6 +74,7 @@ tsdb_SRC := \ src/meta/TSUIDQuery.java \ src/meta/UIDMeta.java \ src/query/QueryUtil.java \ + src/query/expression/Absolute.java \ src/query/expression/HighestCurrent.java \ src/query/expression/HighestMax.java \ src/query/expression/MovingAverage.java \ @@ -223,6 +224,7 @@ test_SRC := \ test/meta/TestTSMeta.java \ test/meta/TestTSUIDQuery.java \ test/meta/TestUIDMeta.java \ + test/query/expression/TestAbsolute.java \ test/query/expression/TestHighestCurrent.java \ test/query/expression/TestHighestMax.java \ test/query/expression/TestMovingAverage.java \ diff --git a/src/query/expression/Absolute.java b/src/query/expression/Absolute.java index 3f9e406ecc..2b3cfb13f1 100644 --- a/src/query/expression/Absolute.java +++ b/src/query/expression/Absolute.java @@ -12,6 +12,7 @@ // see . package net.opentsdb.query.expression; +import java.util.ArrayList; import java.util.List; import net.opentsdb.core.DataPoint; @@ -20,41 +21,64 @@ import net.opentsdb.core.SeekableView; import net.opentsdb.core.TSQuery; +/** + * Modifies each data point in the series with the absolute value, tossing away + * the signed component. + * @since 2.3 + */ public class Absolute implements Expression { @Override - public DataPoints[] evaluate(TSQuery data_query, List queryResults, List params) { - if (queryResults == null || queryResults.isEmpty()) { - throw new NullPointerException("Query results cannot be empty"); + public DataPoints[] evaluate(final TSQuery data_query, + final List query_results, final List params) { + if (data_query == null) { + throw new IllegalArgumentException("Missing time series query"); } - - DataPoints[] inputPoints = queryResults.get(0); - DataPoints[] outputPoints = new DataPoints[inputPoints.length]; - - for (int i=0; i dps = new ArrayList(); - SeekableView view = points.iterator(); - int i=0; + final SeekableView view = points.iterator(); while (view.hasNext()) { DataPoint pt = view.next(); if (pt.isInteger()) { - dps[i] = MutableDataPoint.ofDoubleValue(pt.timestamp(), Math.abs(pt.longValue())); + dps.add(MutableDataPoint.ofLongValue( + pt.timestamp(), Math.abs(pt.longValue()))); } else { - dps[i] = MutableDataPoint.ofDoubleValue(pt.timestamp(), Math.abs(pt.doubleValue())); + dps.add(MutableDataPoint.ofDoubleValue( + pt.timestamp(), Math.abs(pt.doubleValue()))); } - i++; } - - return new PostAggregatedDataPoints(points, dps); + final DataPoint[] results = new DataPoint[dps.size()]; + dps.toArray(results); + return new PostAggregatedDataPoints(points, results); } @Override diff --git a/test/query/expression/TestAbsolute.java b/test/query/expression/TestAbsolute.java new file mode 100644 index 0000000000..3d675ee0d6 --- /dev/null +++ b/test/query/expression/TestAbsolute.java @@ -0,0 +1,307 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import net.opentsdb.core.DataPoint; +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.SeekableView; +import net.opentsdb.core.SeekableViewsForTest; +import net.opentsdb.core.TSQuery; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +@RunWith(PowerMockRunner.class) +@PowerMockIgnore({"javax.management.*", "javax.xml.*", + "ch.qos.*", "org.slf4j.*", + "com.sum.*", "org.xml.*"}) +@PrepareForTest({ TSQuery.class }) +public class TestAbsolute { + + private static long START_TIME = 1356998400000L; + private static int INTERVAL = 60000; + private static int NUM_POINTS = 5; + private static String METRIC = "sys.cpu"; + + private TSQuery data_query; + private SeekableView view; + private DataPoints dps; + private DataPoints[] group_bys; + private List query_results; + private List params; + private Absolute func; + + @Before + public void before() throws Exception { + view = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 1, 1); + data_query = mock(TSQuery.class); + when(data_query.startTime()).thenReturn(START_TIME); + when(data_query.endTime()).thenReturn(START_TIME + (INTERVAL * NUM_POINTS)); + + dps = PowerMockito.mock(DataPoints.class); + when(dps.iterator()).thenReturn(view); + when(dps.metricName()).thenReturn(METRIC); + + group_bys = new DataPoints[] { dps }; + + query_results = new ArrayList(1); + query_results.add(group_bys); + + params = new ArrayList(1); + func = new Absolute(); + } + + @Test + public void evaluatePositiveGroupByLong() throws Exception { + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + group_bys = new DataPoints[] { dps, dps2 }; + query_results.clear(); + query_results.add(group_bys); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(2, results.length); + assertEquals(METRIC, results[0].metricName()); + assertEquals("sys.mem", results[1].metricName()); + + long ts = START_TIME; + long v = 1; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + ts = START_TIME; + v = 10; + for (DataPoint dp : results[1]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + } + + @Test + public void evaluatePositiveGroupByDouble() throws Exception { + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, false, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + group_bys = new DataPoints[] { dps, dps2 }; + query_results.clear(); + query_results.add(group_bys); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(2, results.length); + assertEquals(METRIC, results[0].metricName()); + assertEquals("sys.mem", results[1].metricName()); + + long ts = START_TIME; + double v = 1; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals((long)v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + ts = START_TIME; + v = 10; + for (DataPoint dp : results[1]) { + assertEquals(ts, dp.timestamp()); + assertFalse(dp.isInteger()); + assertEquals(v, dp.doubleValue(), 0.001); + ts += INTERVAL; + v += 1; + } + } + + @Test + public void evaluateFactorNegativeGroupByLong() throws Exception { + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, -10, -1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + group_bys = new DataPoints[] { dps, dps2 }; + query_results.clear(); + query_results.add(group_bys); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(2, results.length); + assertEquals(METRIC, results[0].metricName()); + assertEquals("sys.mem", results[1].metricName()); + + long ts = START_TIME; + long v = 1; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + ts = START_TIME; + v = 10; + for (DataPoint dp : results[1]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + } + + @Test + public void evaluateNegativeGroupByDouble() throws Exception { + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, false, -10, -1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + group_bys = new DataPoints[] { dps, dps2 }; + query_results.clear(); + query_results.add(group_bys); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(2, results.length); + assertEquals(METRIC, results[0].metricName()); + assertEquals("sys.mem", results[1].metricName()); + + long ts = START_TIME; + double v = 1; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals((long)v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + ts = START_TIME; + v = 10; + for (DataPoint dp : results[1]) { + assertEquals(ts, dp.timestamp()); + assertFalse(dp.isInteger()); + assertEquals(v, dp.doubleValue(), 0.001); + ts += INTERVAL; + v += 1; + } + } + + @Test + public void evaluateNegativeSubQuerySeries() throws Exception { + params.add("1"); + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, -10, -1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + group_bys = new DataPoints[] { dps, dps2 }; + query_results.clear(); + query_results.add(group_bys); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(2, results.length); + assertEquals(METRIC, results[0].metricName()); + assertEquals("sys.mem", results[1].metricName()); + + long ts = START_TIME; + long v = 1; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + ts = START_TIME; + v = 10; + for (DataPoint dp : results[1]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateNullQuery() throws Exception { + params.add("1"); + func.evaluate(null, query_results, params); + } + + @Test + public void evaluateNullResults() throws Exception { + params.add("1"); + final DataPoints[] results = func.evaluate(data_query, null, params); + assertEquals(0, results.length); + } + + @Test + public void evaluateNullParams() throws Exception { + assertNotNull(func.evaluate(data_query, query_results, null)); + } + + @Test + public void evaluateEmptyResults() throws Exception { + final DataPoints[] results = func.evaluate(data_query, + Collections.emptyList(), params); + assertEquals(0, results.length); + } + + @Test + public void evaluateEmptyParams() throws Exception { + assertNotNull(func.evaluate(data_query, query_results, null)); + } + + @Test + public void writeStringField() throws Exception { + params.add("1"); + assertEquals("absolute(inner_expression)", + func.writeStringField(params, "inner_expression")); + assertEquals("absolute(null)", func.writeStringField(params, null)); + assertEquals("absolute()", func.writeStringField(params, "")); + assertEquals("absolute(inner_expression)", + func.writeStringField(null, "inner_expression")); + } +} From a6a0a01ce9401f497ef3d76309feec4586566849 Mon Sep 17 00:00:00 2001 From: Arjun Satish Date: Sun, 1 Nov 2015 10:05:29 -0800 Subject: [PATCH 036/233] Add the ExpressionFactory class that stores a map of the available functions. Signed-off-by: Chris Larsen --- src/query/expression/ExpressionFactory.java | 29 +++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/query/expression/ExpressionFactory.java diff --git a/src/query/expression/ExpressionFactory.java b/src/query/expression/ExpressionFactory.java new file mode 100644 index 0000000000..d18b0cf007 --- /dev/null +++ b/src/query/expression/ExpressionFactory.java @@ -0,0 +1,29 @@ +package net.opentsdb.query.expression; + +import java.util.HashMap; +import java.util.Map; + +import com.google.common.annotations.VisibleForTesting; + +public class ExpressionFactory { + + private static Map availableFunctions = + new HashMap(); + + static { + availableFunctions.put("scale", new Scale()); + availableFunctions.put("absolute", new Absolute()); + availableFunctions.put("movingAverage", new MovingAverage()); + availableFunctions.put("highestCurrent", new HighestCurrent()); + availableFunctions.put("highestMax", new HighestMax()); + } + + @VisibleForTesting + static void addFunction(String name, Expression expr) { + availableFunctions.put(name, expr); + } + + public static Expression getByName(String funcName) { + return availableFunctions.get(funcName); + } +} From ebc2181dab6a1b02149df3ae795c63a859ef998a Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sun, 1 Nov 2015 10:11:36 -0800 Subject: [PATCH 037/233] Fixup the ExpressionFactory by adding comments and changing the getter to throw an exception if the requested function isn't found. Also add unit tests Signed-off-by: Chris Larsen --- Makefile.am | 2 + src/query/expression/ExpressionFactory.java | 73 ++++++++++++--- .../expression/TestExpressionFactory.java | 93 +++++++++++++++++++ 3 files changed, 154 insertions(+), 14 deletions(-) create mode 100644 test/query/expression/TestExpressionFactory.java diff --git a/Makefile.am b/Makefile.am index de306c4076..8b4b7741c4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -75,6 +75,7 @@ tsdb_SRC := \ src/meta/UIDMeta.java \ src/query/QueryUtil.java \ src/query/expression/Absolute.java \ + src/query/expression/ExpressionFactory.java \ src/query/expression/HighestCurrent.java \ src/query/expression/HighestMax.java \ src/query/expression/MovingAverage.java \ @@ -225,6 +226,7 @@ test_SRC := \ test/meta/TestTSUIDQuery.java \ test/meta/TestUIDMeta.java \ test/query/expression/TestAbsolute.java \ + test/query/expression/TestExpressionFactory.java \ test/query/expression/TestHighestCurrent.java \ test/query/expression/TestHighestMax.java \ test/query/expression/TestMovingAverage.java \ diff --git a/src/query/expression/ExpressionFactory.java b/src/query/expression/ExpressionFactory.java index d18b0cf007..3c61528dea 100644 --- a/src/query/expression/ExpressionFactory.java +++ b/src/query/expression/ExpressionFactory.java @@ -1,29 +1,74 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . package net.opentsdb.query.expression; import java.util.HashMap; import java.util.Map; -import com.google.common.annotations.VisibleForTesting; - -public class ExpressionFactory { +/** + * A static class that stores and instantiates a static map of the available + * functions. + * TODO - Enable plugable expression and load from the class path. + * Since 2.3 + */ +public final class ExpressionFactory { - private static Map availableFunctions = + private static Map available_functions = new HashMap(); static { - availableFunctions.put("scale", new Scale()); - availableFunctions.put("absolute", new Absolute()); - availableFunctions.put("movingAverage", new MovingAverage()); - availableFunctions.put("highestCurrent", new HighestCurrent()); - availableFunctions.put("highestMax", new HighestMax()); + available_functions.put("scale", new Scale()); + available_functions.put("absolute", new Absolute()); + available_functions.put("movingAverage", new MovingAverage()); + available_functions.put("highestCurrent", new HighestCurrent()); + available_functions.put("highestMax", new HighestMax()); } - @VisibleForTesting - static void addFunction(String name, Expression expr) { - availableFunctions.put(name, expr); + /** Don't instantiate me! */ + private ExpressionFactory() { } + + /** + * Add an expression to the map. + * WARNING: The map is not thread safe so don't use this to dynamically + * modify the map while the TSD is running. + * @param name The name of the expression + * @param expr The expression object to store. + * @throws IllegalArgumentException if the name is null or empty or the + * function is null. + */ + static void addFunction(final String name, final Expression expr) { + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException("Missing function name"); + } + if (expr == null) { + throw new IllegalArgumentException("Function cannot be null"); + } + available_functions.put(name, expr); } - public static Expression getByName(String funcName) { - return availableFunctions.get(funcName); + /** + * Returns the expression function given the name + * @param function The name of the expression to use + * @return The expression when located + * @throws UnsupportedOperationException if the requested function hasn't + * been stored in the map. + */ + public static Expression getByName(final String function) { + final Expression expression = available_functions.get(function); + if (expression == null) { + throw new UnsupportedOperationException("Function " + function + + " has not been implemented"); + } + return expression; } } diff --git a/test/query/expression/TestExpressionFactory.java b/test/query/expression/TestExpressionFactory.java new file mode 100644 index 0000000000..b0024fa44f --- /dev/null +++ b/test/query/expression/TestExpressionFactory.java @@ -0,0 +1,93 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import static org.junit.Assert.assertTrue; + +import java.util.List; + +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.TSQuery; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.modules.junit4.PowerMockRunner; + +@RunWith(PowerMockRunner.class) +@PowerMockIgnore({"javax.management.*", "javax.xml.*", + "ch.qos.*", "org.slf4j.*", + "com.sum.*", "org.xml.*"}) +public class TestExpressionFactory { + + @Test + public void getByName() throws Exception { + // pick a couple of implementations + Expression e = ExpressionFactory.getByName("scale"); + assertTrue(e instanceof Scale); + + e = ExpressionFactory.getByName("highestMax"); + assertTrue(e instanceof HighestMax); + } + + @Test (expected = UnsupportedOperationException.class) + public void getByNameNoSuchFunction() throws Exception { + ExpressionFactory.getByName("I don't exist"); + } + + @Test (expected = UnsupportedOperationException.class) + public void getByNameNullName() throws Exception { + ExpressionFactory.getByName(null); + } + + @Test (expected = UnsupportedOperationException.class) + public void getByNameEmptyName() throws Exception { + ExpressionFactory.getByName(""); + } + + @Test + public void addFunction() throws Exception { + ExpressionFactory.addFunction("testExpr", new TestExpr()); + final Expression e = ExpressionFactory.getByName("testExpr"); + assertTrue(e instanceof TestExpr); + } + + @Test (expected = IllegalArgumentException.class) + public void addFunctionNullName() throws Exception { + ExpressionFactory.addFunction(null, new TestExpr()); + } + + @Test (expected = IllegalArgumentException.class) + public void addFunctionEmptyName() throws Exception { + ExpressionFactory.addFunction("", new TestExpr()); + } + + @Test (expected = IllegalArgumentException.class) + public void addFunctionNullFunction() throws Exception { + ExpressionFactory.addFunction("testExpr", null); + } + + /** Dummy expression class used for testing */ + private static class TestExpr implements Expression { + @Override + public DataPoints[] evaluate(TSQuery data_query, + List results, List params) { + return null; + } + @Override + public String writeStringField(List params, + String inner_expression) { + return null; + } + } +} From 97f662c457fcaf6fcace46c7495c4c138147d349 Mon Sep 17 00:00:00 2001 From: Arjun Satish Date: Sun, 1 Nov 2015 10:24:36 -0800 Subject: [PATCH 038/233] Add the expression tree to store the processing order for functions from the Graphite endpoint. Signed-off-by: Chris Larsen --- src/query/expression/ExpressionTree.java | 151 +++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 src/query/expression/ExpressionTree.java diff --git a/src/query/expression/ExpressionTree.java b/src/query/expression/ExpressionTree.java new file mode 100644 index 0000000000..6a48300f78 --- /dev/null +++ b/src/query/expression/ExpressionTree.java @@ -0,0 +1,151 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import com.google.common.base.Joiner; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.TSQuery; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class ExpressionTree { + + private final Expression expr; + private final TSQuery data_query; + + private List subExpressions; + private List funcParams; + private Map subMetricQueries; + private Map parameterSourceIndex = Maps.newHashMap(); + + private static final Joiner DOUBLE_COMMA_JOINER = Joiner.on(",").skipNulls(); + + enum Parameter { + SUB_EXPRESSION, + METRIC_QUERY + } + + public ExpressionTree(String exprName, TSQuery data_query) { + this(ExpressionFactory.getByName(exprName), data_query); + } + + public ExpressionTree(Expression expr, TSQuery data_query) { + this.expr = expr; + this.data_query = data_query; + } + + public void addSubExpression(ExpressionTree child, int paramIndex) { + if (subExpressions == null) { + subExpressions = Lists.newArrayList(); + } + subExpressions.add(child); + parameterSourceIndex.put(paramIndex, Parameter.SUB_EXPRESSION); + } + + public void addSubMetricQuery(String metricQuery, int magic, + int paramIndex) { + if (subMetricQueries == null) { + subMetricQueries = Maps.newHashMap(); + } + subMetricQueries.put(magic, metricQuery); + parameterSourceIndex.put(paramIndex, Parameter.METRIC_QUERY); + } + + public void addFunctionParameter(String param) { + if (funcParams == null) { + funcParams = Lists.newArrayList(); + } + funcParams.add(param); + } + + public DataPoints[] evaluate(List queryResults) { + List materialized = Lists.newArrayList(); + List metricQueryKeys = null; + if (subMetricQueries != null && subMetricQueries.size() > 0) { + metricQueryKeys = Lists.newArrayList(subMetricQueries.keySet()); + Collections.sort(metricQueryKeys); + } + + int metricPointer = 0; + int subExprPointer = 0; + for (int i=0; i strs = Lists.newArrayList(); + if (subExpressions != null) { + for (ExpressionTree sub : subExpressions) { + strs.add(sub.toString()); + } + } + + if (subMetricQueries != null) { + String subMetrics = clean(subMetricQueries.values()); + if (subMetrics != null && subMetrics.length() > 0) { + strs.add(subMetrics); + } + } + + String innerExpression = DOUBLE_COMMA_JOINER.join(strs); + return expr.writeStringField(funcParams, innerExpression); + } + + private String clean(Collection values) { + if (values == null || values.size() == 0) { + return ""; + } + + List strs = Lists.newArrayList(); + for (String v : values) { + String tmp = v.replaceAll("\\{.*\\}", ""); + int ix = tmp.lastIndexOf(':'); + if (ix < 0) { + strs.add(tmp); + } else { + strs.add(tmp.substring(ix+1)); + } + } + + return DOUBLE_COMMA_JOINER.join(strs); + } + +} \ No newline at end of file From 0fed45f0ad401ed9631c74f9e4db77075f8dc4f2 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sun, 1 Nov 2015 12:38:18 -0800 Subject: [PATCH 039/233] Cleanup the ExpressionTree class a bit, add unit tests and comments. I think this class can be cleaned up further, particulary with better ctors and index tracking but we'll add the rest of the expression support classes and see how it goes. Signed-off-by: Chris Larsen --- Makefile.am | 2 + src/query/expression/ExpressionTree.java | 238 +++++++++---- test/query/expression/TestExpressionTree.java | 319 ++++++++++++++++++ 3 files changed, 493 insertions(+), 66 deletions(-) create mode 100644 test/query/expression/TestExpressionTree.java diff --git a/Makefile.am b/Makefile.am index 8b4b7741c4..61d9e199de 100644 --- a/Makefile.am +++ b/Makefile.am @@ -76,6 +76,7 @@ tsdb_SRC := \ src/query/QueryUtil.java \ src/query/expression/Absolute.java \ src/query/expression/ExpressionFactory.java \ + src/query/expression/ExpressionTree.java \ src/query/expression/HighestCurrent.java \ src/query/expression/HighestMax.java \ src/query/expression/MovingAverage.java \ @@ -227,6 +228,7 @@ test_SRC := \ test/meta/TestUIDMeta.java \ test/query/expression/TestAbsolute.java \ test/query/expression/TestExpressionFactory.java \ + test/query/expression/TestExpressionTree.java \ test/query/expression/TestHighestCurrent.java \ test/query/expression/TestHighestMax.java \ test/query/expression/TestMovingAverage.java \ diff --git a/src/query/expression/ExpressionTree.java b/src/query/expression/ExpressionTree.java index 6a48300f78..28bc5feaf2 100644 --- a/src/query/expression/ExpressionTree.java +++ b/src/query/expression/ExpressionTree.java @@ -12,10 +12,13 @@ // see . package net.opentsdb.query.expression; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; import com.google.common.collect.Lists; import com.google.common.collect.Maps; + import net.opentsdb.core.DataPoints; +import net.opentsdb.core.IllegalDataException; import net.opentsdb.core.TSQuery; import java.util.Collection; @@ -23,121 +26,205 @@ import java.util.List; import java.util.Map; +/** + * A node in a tree of nested expressions. The tree may link to other nodes as + * sub expressions. Evaluating the tree evaluates all sub expressions. + *

+ * Before calling {@link evaluate} you MUST call a one or a combination of + * {@link addSubExpression}, {@link addSubMetricQuery} and optionally + * {@link addFunctionParameter} + *

+ * TODO(cl) - Cleanup needed. Tracking the indices can likely be done better + * and it would be good to have a ctor that sets the sub or metric query. + * @since 2.3 + */ public class ExpressionTree { - - private final Expression expr; - private final TSQuery data_query; - - private List subExpressions; - private List funcParams; - private Map subMetricQueries; - private Map parameterSourceIndex = Maps.newHashMap(); - + /** Used for the toString() helpers */ private static final Joiner DOUBLE_COMMA_JOINER = Joiner.on(",").skipNulls(); - + + /** An enumerator of the different query types */ enum Parameter { SUB_EXPRESSION, METRIC_QUERY } + + /** The root expression for the tree */ + private final Expression expression; + /** The original time series query */ + private final TSQuery data_query; + /** An optional list of sub expressions */ + private List sub_expressions; + /** A list of parameters for the root expression */ + private List func_params; + /** A mapping of result indices to sub metric queries */ + private Map sub_metric_queries; + /** A mapping of query types to their result index */ + private Map parameter_index = Maps.newHashMap(); - public ExpressionTree(String exprName, TSQuery data_query) { - this(ExpressionFactory.getByName(exprName), data_query); + /** + * Creates a tree with a root and no children + * @param expression_name The name of the expression to lookup in the factory + * @param data_query The original query + * @throws UnsupportedOperationException if the expression is not implemented + */ + public ExpressionTree(final String expression_name, final TSQuery data_query) { + this(ExpressionFactory.getByName(expression_name), data_query); } - - public ExpressionTree(Expression expr, TSQuery data_query) { - this.expr = expr; + + /** + * Creates a tree with a root and no children + * @param expression The expression to use + * @param data_query The original query + */ + public ExpressionTree(final Expression expression, final TSQuery data_query) { + this.expression = expression; this.data_query = data_query; } - public void addSubExpression(ExpressionTree child, int paramIndex) { - if (subExpressions == null) { - subExpressions = Lists.newArrayList(); + public void addSubExpression(final ExpressionTree child, final int param_index) { + if (child == null) { + throw new IllegalArgumentException("Cannot add a null child tree"); + } + if (child == this) { + throw new IllegalDataException("Recursive sub expression detected: " + + this); } - subExpressions.add(child); - parameterSourceIndex.put(paramIndex, Parameter.SUB_EXPRESSION); + if (param_index < 0) { + throw new IllegalArgumentException("Parameter index must be 0 or greater"); + } + if (sub_expressions == null) { + sub_expressions = Lists.newArrayList(); + } + sub_expressions.add(child); + parameter_index.put(param_index, Parameter.SUB_EXPRESSION); } - public void addSubMetricQuery(String metricQuery, int magic, - int paramIndex) { - if (subMetricQueries == null) { - subMetricQueries = Maps.newHashMap(); + /** + * Sets the metric query key and index, setting the Parameter type to + * METRIC_QUERY + * @param metric_query The metric query id + * @param sub_query_index The index of the metric query + * @param param_index The index of the parameter (??) + */ + public void addSubMetricQuery(final String metric_query, + final int sub_query_index, + final int param_index) { + if (metric_query == null || metric_query.isEmpty()) { + throw new IllegalArgumentException("Metric query cannot be null or empty"); + } + if (sub_query_index < 0) { + throw new IllegalArgumentException("Sub query index must be 0 or greater"); } - subMetricQueries.put(magic, metricQuery); - parameterSourceIndex.put(paramIndex, Parameter.METRIC_QUERY); + if (param_index < 0) { + throw new IllegalArgumentException("Parameter index must be 0 or greater"); + } + if (sub_metric_queries == null) { + sub_metric_queries = Maps.newHashMap(); + } + sub_metric_queries.put(sub_query_index, metric_query); + parameter_index.put(param_index, Parameter.METRIC_QUERY); } - - public void addFunctionParameter(String param) { - if (funcParams == null) { - funcParams = Lists.newArrayList(); + + /** + * Adds parameters for the root expression only. + * @param param The parameter to add, cannot be null or empty + * @throws IllegalArgumentException if the parameter is null or empty + */ + public void addFunctionParameter(final String param) { + if (param == null || param.isEmpty()) { + throw new IllegalArgumentException("Parameter cannot be null or empty"); } - funcParams.add(param); + if (func_params == null) { + func_params = Lists.newArrayList(); + } + func_params.add(param); } - public DataPoints[] evaluate(List queryResults) { - List materialized = Lists.newArrayList(); - List metricQueryKeys = null; - if (subMetricQueries != null && subMetricQueries.size() > 0) { - metricQueryKeys = Lists.newArrayList(subMetricQueries.keySet()); - Collections.sort(metricQueryKeys); + /** + * Processes the expression tree, including sub expressions, and returns the + * results. + * TODO(cl) - More tests around indices, etc. This can likely be cleaned up. + * @param query_results The result set to pass to the expressions + * @return The result set or an exception will bubble up if something wasn't + * configured properly. + */ + public DataPoints[] evaluate(final List query_results) { + // TODO - size the array + final List materialized = Lists.newArrayList(); + List metric_query_keys = null; + if (sub_metric_queries != null && sub_metric_queries.size() > 0) { + metric_query_keys = Lists.newArrayList(sub_metric_queries.keySet()); + Collections.sort(metric_query_keys); } - int metricPointer = 0; - int subExprPointer = 0; - for (int i=0; i strs = Lists.newArrayList(); - if (subExpressions != null) { - for (ExpressionTree sub : subExpressions) { + final List strs = Lists.newArrayList(); + if (sub_expressions != null) { + for (ExpressionTree sub : sub_expressions) { strs.add(sub.toString()); } } - if (subMetricQueries != null) { - String subMetrics = clean(subMetricQueries.values()); - if (subMetrics != null && subMetrics.length() > 0) { - strs.add(subMetrics); + if (sub_metric_queries != null) { + final String sub_metrics = clean(sub_metric_queries.values()); + if (sub_metrics != null && sub_metrics.length() > 0) { + strs.add(sub_metrics); } } - String innerExpression = DOUBLE_COMMA_JOINER.join(strs); - return expr.writeStringField(funcParams, innerExpression); + final String inner_expression = DOUBLE_COMMA_JOINER.join(strs); + return expression.writeStringField(func_params, inner_expression); } - private String clean(Collection values) { + /** + * Helper to clean out some characters + * @param values The collection of strings to cleanup + * @return An empty string if values was empty or a cleaned up string + */ + private String clean(final Collection values) { if (values == null || values.size() == 0) { return ""; } - List strs = Lists.newArrayList(); + final List strs = Lists.newArrayList(); for (String v : values) { - String tmp = v.replaceAll("\\{.*\\}", ""); - int ix = tmp.lastIndexOf(':'); + final String tmp = v.replaceAll("\\{.*\\}", ""); + final int ix = tmp.lastIndexOf(':'); if (ix < 0) { strs.add(tmp); } else { @@ -148,4 +235,23 @@ private String clean(Collection values) { return DOUBLE_COMMA_JOINER.join(strs); } + @VisibleForTesting + List subExpressions() { + return sub_expressions; + } + + @VisibleForTesting + List funcParams() { + return func_params; + } + + @VisibleForTesting + Map subMetricQueries() { + return sub_metric_queries; + } + + @VisibleForTesting + Map parameterIndex() { + return parameter_index; + } } \ No newline at end of file diff --git a/test/query/expression/TestExpressionTree.java b/test/query/expression/TestExpressionTree.java new file mode 100644 index 0000000000..ad1a5f37c7 --- /dev/null +++ b/test/query/expression/TestExpressionTree.java @@ -0,0 +1,319 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.List; + +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.IllegalDataException; +import net.opentsdb.core.SeekableView; +import net.opentsdb.core.SeekableViewsForTest; +import net.opentsdb.core.TSQuery; +import net.opentsdb.query.expression.ExpressionTree.Parameter; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +@RunWith(PowerMockRunner.class) +@PowerMockIgnore({"javax.management.*", "javax.xml.*", + "ch.qos.*", "org.slf4j.*", + "com.sum.*", "org.xml.*"}) +@PrepareForTest({ TSQuery.class }) +public class TestExpressionTree { + private final static String EXPR_NAME = "treeTestExpr"; + private static long START_TIME = 1356998400000L; + private static int INTERVAL = 60000; + private static int NUM_POINTS = 5; + private static String METRIC = "sys.cpu"; + + private TSQuery data_query; + private SeekableView view; + private DataPoints dps; + private DataPoints[] group_bys; + private List query_results; + private TreeTestExpr test_expression; + + @Before + public void before() throws Exception { + view = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 1, 1); + data_query = mock(TSQuery.class); + when(data_query.startTime()).thenReturn(START_TIME); + when(data_query.endTime()).thenReturn(START_TIME + (INTERVAL * NUM_POINTS)); + + dps = PowerMockito.mock(DataPoints.class); + when(dps.iterator()).thenReturn(view); + when(dps.metricName()).thenReturn(METRIC); + + group_bys = new DataPoints[] { dps }; + + query_results = new ArrayList(1); + query_results.add(group_bys); + + test_expression = new TreeTestExpr(); + ExpressionFactory.addFunction(EXPR_NAME, test_expression); + } + + @Test + public void ctorString() throws Exception { + final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query); + assertEquals(EXPR_NAME + "()", tree.toString()); + assertNull(tree.subExpressions()); + assertNull(tree.funcParams()); + assertNull(tree.subMetricQueries()); + assertTrue(tree.parameterIndex().isEmpty()); + } + + @Test (expected = UnsupportedOperationException.class) + public void ctorStringNull() throws Exception { + new ExpressionTree((String)null, data_query); + } + + @Test (expected = UnsupportedOperationException.class) + public void ctorStringEmpty() throws Exception { + new ExpressionTree("", data_query); + } + + @Test (expected = UnsupportedOperationException.class) + public void ctorStringUnknown() throws Exception { + new ExpressionTree("No such method", data_query); + } + + @Test + public void ctorExpression() throws Exception { + final ExpressionTree tree = new ExpressionTree(test_expression, data_query); + assertEquals(EXPR_NAME + "()", tree.toString()); + assertNull(tree.subExpressions()); + assertNull(tree.funcParams()); + assertNull(tree.subMetricQueries()); + assertTrue(tree.parameterIndex().isEmpty()); + } + + @Test + public void addSubExpression() throws Exception { + final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query); + final ExpressionTree child = new ExpressionTree("scale", data_query); + tree.addSubExpression(child, 1); + assertEquals(1, tree.subExpressions().size()); + assertSame(child, tree.subExpressions().get(0)); + assertNull(tree.funcParams()); + assertNull(tree.subMetricQueries()); + assertEquals(1, tree.parameterIndex().size()); + assertEquals(Parameter.SUB_EXPRESSION, tree.parameterIndex().get(1)); + } + + @Test (expected = IllegalArgumentException.class) + public void addSubExpressionNullTree() throws Exception { + final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query); + tree.addSubExpression(null, 1); + } + + @Test (expected = IllegalArgumentException.class) + public void addSubExpressionNegativeIndex() throws Exception { + final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query); + tree.addSubExpression(null, 1); + } + + @Test (expected = IllegalDataException.class) + public void addSubExpressionRecursion() throws Exception { + final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query); + tree.addSubExpression(tree, 1); + } + + @Test + public void addSubMetricQuery() throws Exception { + final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query); + tree.addSubMetricQuery(METRIC, 1, 1); + assertEquals(EXPR_NAME + "(" + METRIC + ")", tree.toString()); + assertNull(tree.subExpressions()); + assertNull(tree.funcParams()); + assertEquals(1, tree.subMetricQueries().size()); + assertEquals(METRIC, tree.subMetricQueries().get(1)); + assertEquals(1, tree.parameterIndex().size()); + assertEquals(Parameter.METRIC_QUERY, tree.parameterIndex().get(1)); + } + + @Test (expected = IllegalArgumentException.class) + public void addSubMetricQueryNullMetric() throws Exception { + final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query); + tree.addSubMetricQuery(null, 1, 1); + } + + @Test (expected = IllegalArgumentException.class) + public void addSubMetricQueryEmptyMetric() throws Exception { + final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query); + tree.addSubMetricQuery("", 1, 1); + } + + @Test (expected = IllegalArgumentException.class) + public void addSubMetricQueryNegativeQueryIndex() throws Exception { + final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query); + tree.addSubMetricQuery(METRIC, -1, 1); + } + + @Test (expected = IllegalArgumentException.class) + public void addSubMetricQueryNegativeParamIndex() throws Exception { + final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query); + tree.addSubMetricQuery(METRIC, 1, -1); + } + + @Test + public void addFunctionParameter() throws Exception { + final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query); + tree.addFunctionParameter("vimes"); + assertEquals(EXPR_NAME + "()", tree.toString()); + assertNull(tree.subExpressions()); + assertEquals(1, tree.funcParams().size()); + assertEquals("vimes", tree.funcParams().get(0)); + assertNull(tree.subMetricQueries()); + assertTrue(tree.parameterIndex().isEmpty()); + } + + @Test (expected = IllegalArgumentException.class) + public void addFunctionParameterNull() throws Exception { + final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query); + tree.addFunctionParameter(null); + } + + @Test (expected = IllegalArgumentException.class) + public void addFunctionParameterEmpty() throws Exception { + final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query); + tree.addFunctionParameter(""); + } + + @Test + public void evaluateNothingSet() throws Exception { + final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query); + assertEquals(EXPR_NAME + "()", tree.toString()); + + final DataPoints[] response = tree.evaluate(query_results); + assertEquals(1, response.length); + assertSame(data_query, test_expression.data_query); + assertEquals(0, test_expression.results.size()); + assertNull(test_expression.params); + } + + @Test + public void evaluateSubMetricQuerySet() throws Exception { + final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query); + tree.addSubMetricQuery(METRIC, 0, 0); + assertEquals(EXPR_NAME + "(" + METRIC + ")", tree.toString()); + + final DataPoints[] response = tree.evaluate(query_results); + assertEquals(1, response.length); + assertSame(data_query, test_expression.data_query); + assertEquals(1, test_expression.results.size()); + assertNull(test_expression.params); + } + + @Test + public void evaluateSubMetricQuerySetWithParam() throws Exception { + final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query); + tree.addSubMetricQuery(METRIC, 0, 0); + tree.addFunctionParameter("foo"); + assertEquals(EXPR_NAME + "(" + METRIC + ")", tree.toString()); + + final DataPoints[] response = tree.evaluate(query_results); + assertEquals(1, response.length); + assertSame(data_query, test_expression.data_query); + assertEquals(1, test_expression.results.size()); + assertEquals(1, test_expression.params.size()); + assertEquals("foo", test_expression.params.get(0)); + } + + @Test + public void evaluateSubExpressionSet() throws Exception { + final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query); + final ExpressionTree child = spy(new ExpressionTree("scale", data_query)); + child.addSubMetricQuery(METRIC, 0, 0); + child.addFunctionParameter("1"); + tree.addSubExpression(child, 0); + assertEquals(EXPR_NAME + "(scale(" + METRIC + "))", tree.toString()); + + final DataPoints[] response = tree.evaluate(query_results); + assertEquals(1, response.length); + assertSame(data_query, test_expression.data_query); + assertEquals(1, test_expression.results.size()); + assertNull(test_expression.params); + verify(child, times(1)).evaluate(query_results); + } + +// TODO - fix this up +// @Test +// public void evaluateSubExpressionAndSubMetricSet() throws Exception { +// final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query); +// final ExpressionTree child = spy(new ExpressionTree("scale", data_query)); +// child.addSubMetricQuery(METRIC, 0, 0); +// child.addFunctionParameter("1"); +// tree.addSubExpression(child, 0); +// +// tree.addSubMetricQuery(METRIC, 1, 0); +// tree.addFunctionParameter("foo"); +// +// assertEquals(EXPR_NAME + "(scale(" + METRIC + ")," + METRIC + ")", +// tree.toString()); +// +// final DataPoints[] response = tree.evaluate(query_results); +// assertEquals(1, response.length); +// assertSame(data_query, test_expression.data_query); +// assertEquals(1, test_expression.results.size()); +// assertEquals(1, test_expression.params.size()); +// assertEquals("foo", test_expression.params.get(0)); +// verify(child, times(1)).evaluate(query_results); +// } + + // TODO - more tests around indexes, etc unless we cleanup the class + + private class TreeTestExpr implements Expression { + TSQuery data_query; + List results; + List params; + + @Override + public DataPoints[] evaluate(TSQuery data_query, + List results, List params) { + this.data_query = data_query; + this.results = results; + this.params = params; + + // Returns an array the size of the total incoming results array + int num_results = 0; + for (DataPoints[] r: query_results) { + num_results += r.length; + } + final DataPoints[] response = new DataPoints[num_results]; + return response; + } + @Override + public String writeStringField(List params, + String inner_expression) { + return EXPR_NAME + "(" + inner_expression + ")"; + } + } +} From 726933b9aa351a04882e9317aa550885c4e38489 Mon Sep 17 00:00:00 2001 From: Arjun Satish Date: Sun, 1 Nov 2015 13:00:56 -0800 Subject: [PATCH 040/233] Add the ExpressionReader class for parsing expressions. Signed-off-by: Chris Larsen --- src/query/expression/ExpressionReader.java | 94 ++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 src/query/expression/ExpressionReader.java diff --git a/src/query/expression/ExpressionReader.java b/src/query/expression/ExpressionReader.java new file mode 100644 index 0000000000..dfcc8a5766 --- /dev/null +++ b/src/query/expression/ExpressionReader.java @@ -0,0 +1,94 @@ +package net.opentsdb.query.expression; + +import com.google.common.base.Preconditions; + +public class ExpressionReader { + + protected final char[] chars; + + private int mark = 0; + + public ExpressionReader(char[] chars) { + Preconditions.checkNotNull(chars); + this.chars = chars; + } + + public int getMark() { + return mark; + } + + public char peek() { + return chars[mark]; + } + + public char next() { + return chars[mark++]; + } + + public void skip(int num) { + mark+=num; + } + + public boolean isNextChar(char c) { + return peek() == c; + } + + public boolean isNextSeq(CharSequence seq) { + Preconditions.checkNotNull(seq); + for (int i=0; i Date: Sun, 1 Nov 2015 13:48:50 -0800 Subject: [PATCH 041/233] Cleanup the ExpressionReader class with comments, formatting, EOF checks and unit tests. Signed-off-by: Chris Larsen --- Makefile.am | 2 + src/query/expression/ExpressionReader.java | 160 ++++++++++----- .../expression/TestExpressionReader.java | 193 ++++++++++++++++++ 3 files changed, 306 insertions(+), 49 deletions(-) create mode 100644 test/query/expression/TestExpressionReader.java diff --git a/Makefile.am b/Makefile.am index 61d9e199de..df63c21a19 100644 --- a/Makefile.am +++ b/Makefile.am @@ -76,6 +76,7 @@ tsdb_SRC := \ src/query/QueryUtil.java \ src/query/expression/Absolute.java \ src/query/expression/ExpressionFactory.java \ + src/query/expression/ExpressionReader.java \ src/query/expression/ExpressionTree.java \ src/query/expression/HighestCurrent.java \ src/query/expression/HighestMax.java \ @@ -228,6 +229,7 @@ test_SRC := \ test/meta/TestUIDMeta.java \ test/query/expression/TestAbsolute.java \ test/query/expression/TestExpressionFactory.java \ + test/query/expression/TestExpressionReader.java \ test/query/expression/TestExpressionTree.java \ test/query/expression/TestHighestCurrent.java \ test/query/expression/TestHighestMax.java \ diff --git a/src/query/expression/ExpressionReader.java b/src/query/expression/ExpressionReader.java index dfcc8a5766..01cf7e0105 100644 --- a/src/query/expression/ExpressionReader.java +++ b/src/query/expression/ExpressionReader.java @@ -1,94 +1,156 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . package net.opentsdb.query.expression; -import com.google.common.base.Preconditions; +import java.util.NoSuchElementException; +/** + * Parses a Graphite style expression. + * Please use {@link #isEOF()} before any method call. Otherwise the methods + * will throw a NoSuchElementException. + * @since 2.3 + */ public class ExpressionReader { - + /** The character array to parse */ protected final char[] chars; + /** The current index in the character array */ private int mark = 0; - public ExpressionReader(char[] chars) { - Preconditions.checkNotNull(chars); - this.chars = chars; + /** + * Default ctor + * @param chars The characters to parse + */ + public ExpressionReader(final char[] chars) { + if (chars == null) { + throw new IllegalArgumentException("Character set cannot be null"); + } + this.chars = chars; } + /** @return the current index */ public int getMark() { - return mark; + return mark; } + /** @return the current character without advancing the index */ public char peek() { - return chars[mark]; + if (isEOF()) { + throw new NoSuchElementException("Index " + mark + " is out of bounds " + + chars.length); + } + return chars[mark]; } + /** @return the current character and advances the index */ public char next() { - return chars[mark++]; + if (isEOF()) { + throw new NoSuchElementException("Index " + mark + " is out of bounds " + + chars.length); + } + return chars[mark++]; } - public void skip(int num) { - mark+=num; + /** @param the number of characters to skip */ + public void skip(final int num) { + if (num < 0) { + throw new UnsupportedOperationException("Skipping backwards is not allowed"); + } + mark += num; } - public boolean isNextChar(char c) { - return peek() == c; + /** + * Checks to see if the next character matches the parameter + * @param c The character to check for + * @return True if they match, false if not + */ + public boolean isNextChar(final char c) { + return peek() == c; } - public boolean isNextSeq(CharSequence seq) { - Preconditions.checkNotNull(seq); - for (int i=0; i= chars.length) { + return false; + } + if (chars[mark + i] != seq.charAt(i)) { + return false; } + } - return true; + return true; } + /** @return the name of the function */ public String readFuncName() { - StringBuilder builder = new StringBuilder(); - while (peek() != '(' && !Character.isWhitespace(peek())) { - builder.append(next()); - } - return builder.toString(); + // in case we get something like " function(foo)" consume a bit + skipWhitespaces(); + StringBuilder builder = new StringBuilder(); + while (peek() != '(' && !Character.isWhitespace(peek())) { + builder.append(next()); + } + skipWhitespaces(); // increment over whitespace after + return builder.toString(); } + /** @return Whether or not the index is at the end of the character array */ public boolean isEOF() { - return mark == chars.length; + return mark >= chars.length; } + /** Increments the mark over white spaces */ public void skipWhitespaces() { - for (int i=mark; i Date: Mon, 2 Nov 2015 11:54:58 -0800 Subject: [PATCH 042/233] Add missing copyright header to TestExpressionReader Signed-off-by: Chris Larsen --- test/query/expression/TestExpressionReader.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/query/expression/TestExpressionReader.java b/test/query/expression/TestExpressionReader.java index 632e37b168..3e4e7bd653 100644 --- a/test/query/expression/TestExpressionReader.java +++ b/test/query/expression/TestExpressionReader.java @@ -1,3 +1,15 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . package net.opentsdb.query.expression; import static org.junit.Assert.assertEquals; From 2b192bc4891f1bb50cfb2b2d540477624282ffa2 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Fri, 30 Oct 2015 20:26:34 -0700 Subject: [PATCH 043/233] Add support for compiling with Cassandra support using AsyncCassandra Signed-off-by: Chris Larsen --- Makefile.am | 12 +++++++ build-cassandra.sh | 9 ++++++ configure.ac | 12 ++++++- pom.xml.in | 18 +++++++++++ src/core/Aggregators.java | 32 +++++++++++++++++++ ...102.192826-2-jar-with-dependencies.jar.md5 | 1 + third_party/asynccassandra/include.mk | 23 +++++++++++++ third_party/include.mk | 9 ++++++ 8 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 build-cassandra.sh create mode 100644 third_party/asynccassandra/asynccassandra-0.0.1-20151102.192826-2-jar-with-dependencies.jar.md5 create mode 100644 third_party/asynccassandra/include.mk diff --git a/Makefile.am b/Makefile.am index df63c21a19..1f078b8338 100644 --- a/Makefile.am +++ b/Makefile.am @@ -180,6 +180,14 @@ tsdb_DEPS += \ $(ASYNCBIGTABLE) maven_profile_bigtable := true maven_profile_hbase := false +maven_profile_cassandra := false +else +if CASSANDRA +tsdb_DEPS += \ + $(ASYNCCASSANDRA) +maven_profile_bigtable := false +maven_profile_hbase := false +maven_profile_cassandra := true else tsdb_DEPS += \ $(ASYNCHBASE) \ @@ -187,6 +195,8 @@ tsdb_DEPS += \ $(ZOOKEEPER) maven_profile_bigtable := false maven_profile_hbase := true +maven_profile_cassandra := false +endif endif test_SRC := \ @@ -695,6 +705,7 @@ pom.xml: pom.xml.in Makefile sed <$< \ -e 's/@ASYNCHBASE_VERSION@/$(ASYNCHBASE_VERSION)/' \ -e 's/@ASYNCBIGTABLE_VERSION@/$(ASYNCBIGTABLE_VERSION)/' \ + -e 's/@ASYNCCASSANDRA_VERSION@/$(ASYNCCASSANDRA_VERSION)/' \ -e 's/@GUAVA_VERSION@/$(GUAVA_VERSION)/' \ -e 's/@GWT_VERSION@/$(GWT_VERSION)/' \ -e 's/@HAMCREST_VERSION@/$(HAMCREST_VERSION)/' \ @@ -717,6 +728,7 @@ pom.xml: pom.xml.in Makefile -e 's/@spec_version@/$(PACKAGE_VERSION)/' \ -e 's/@maven_profile_hbase@/$(maven_profile_hbase)/' \ -e 's/@maven_profile_bigtable@/$(maven_profile_bigtable)/' \ + -e 's/@maven_profile_cassandrae@/$(maven_profile_cassandra)/' \ ; \ } >$@-t mv $@-t ../$@ diff --git a/build-cassandra.sh b/build-cassandra.sh new file mode 100644 index 0000000000..a29a383e34 --- /dev/null +++ b/build-cassandra.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -xe +test -f configure || ./bootstrap +test -d build || mkdir build +cd build +test -f Makefile || ../configure --with-cassandra "$@" +MAKE=make +[ `uname -s` = "FreeBSD" ] && MAKE=gmake +exec ${MAKE} "$@" \ No newline at end of file diff --git a/configure.ac b/configure.ac index dea9bd6147..8212e4ffe6 100644 --- a/configure.ac +++ b/configure.ac @@ -25,7 +25,7 @@ AC_CONFIG_FILES([opentsdb.spec]) AC_CONFIG_FILES([build-aux/fetchdep.sh], [chmod +x build-aux/fetchdep.sh]) AC_ARG_WITH([bigtable], - [AS_HELP_STRING([--with-bigtable], [enable bigtable backend])], + [AS_HELP_STRING([--with-bigtable], [Enable Google's Bigtable backend])], [with_bigtable=yes], [with_bigtable=no]) @@ -34,6 +34,16 @@ AS_IF([test "x$with_bigtable" = "xyes"], [AM_CONDITIONAL(BIGTABLE, false)] ) +AC_ARG_WITH([cassandra], + [AS_HELP_STRING([--with-cassandra], [Enable Cassandra backend])], + [with_cassandra=yes], + [with_cassandra=no]) + +AS_IF([test "x$with_cassandra" = "xyes"], + [AM_CONDITIONAL(CASSANDRA, true)], + [AM_CONDITIONAL(CASSANDRA, false)] +) + TSDB_FIND_PROG([md5], [md5sum md5 gmd5sum digest]) if test x`basename "$MD5"` = x'digest'; then MD5='digest -a md5' diff --git a/pom.xml.in b/pom.xml.in index fee38d15af..cf68ef34e6 100644 --- a/pom.xml.in +++ b/pom.xml.in @@ -510,6 +510,24 @@ + + + + cassandra + + @maven_profile_cassandra@ + + + + + net.opentsdb + asynccassandra + @ASYNCCASSANDRA_VERSION@ + jar-with-dependencies + + + + diff --git a/src/core/Aggregators.java b/src/core/Aggregators.java index d70872e0de..9cc26f5108 100644 --- a/src/core/Aggregators.java +++ b/src/core/Aggregators.java @@ -77,6 +77,11 @@ public enum Interpolation { public static final Aggregator MIMMAX = new Max( Interpolation.MIN, "mimmax"); + /** Return the product of two time series + * @since 2.3 */ + public static final Aggregator MULTIPLY = new Multiply( + Interpolation.LERP, "multiply"); + /** Aggregator that returns the number of data points. * WARNING: This currently interpolates with zero-if-missing. In this case * counts will be off when counting multiple time series. Only use this when @@ -150,6 +155,7 @@ public enum Interpolation { aggregators.put("zimsum", ZIMSUM); aggregators.put("mimmin", MIMMIN); aggregators.put("mimmax", MIMMAX); + aggregators.put("multiply", MULTIPLY); PercentileAgg[] percentiles = { p999, p99, p95, p90, p75, p50, @@ -518,4 +524,30 @@ public double runDouble(final Doubles values) { } } + + private static final class Multiply extends Aggregator { + + public Multiply(final Interpolation method, final String name) { + super(method, name); + } + + @Override + public long runLong(Longs values) { + long result = values.nextLongValue(); + while (values.hasNextValue()) { + result *= values.nextLongValue(); + } + return result; + } + + @Override + public double runDouble(Doubles values) { + double result = values.nextDoubleValue(); + while (values.hasNextValue()) { + double d = values.nextDoubleValue(); + result *= d; + } + return result; + } + } } diff --git a/third_party/asynccassandra/asynccassandra-0.0.1-20151102.192826-2-jar-with-dependencies.jar.md5 b/third_party/asynccassandra/asynccassandra-0.0.1-20151102.192826-2-jar-with-dependencies.jar.md5 new file mode 100644 index 0000000000..10cf1edf3e --- /dev/null +++ b/third_party/asynccassandra/asynccassandra-0.0.1-20151102.192826-2-jar-with-dependencies.jar.md5 @@ -0,0 +1 @@ +cce1a4b5736fcdcc3ced33982c3069d1 \ No newline at end of file diff --git a/third_party/asynccassandra/include.mk b/third_party/asynccassandra/include.mk new file mode 100644 index 0000000000..4d8658a262 --- /dev/null +++ b/third_party/asynccassandra/include.mk @@ -0,0 +1,23 @@ +# Copyright (C) 2015 The OpenTSDB Authors. +# +# This library is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 2.1 of the License, or +# (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . + +ASYNCCASSANDRA_VERSION := 0.0.1-20151102.192826-2 +ASYNCCASSANDRA := third_party/asynccassandra/asynccassandra-$(ASYNCCASSANDRA_VERSION)-jar-with-dependencies.jar +ASYNCCASSANDRA_BASE_URL := https://oss.sonatype.org/content/repositories/snapshots/net/opentsdb/asynccassandra/0.0.1-SNAPSHOT/ + +$(ASYNCCASSANDRA): $(ASYNCCASSANDRA).md5 + set dummy "$(ASYNCCASSANDRA_BASE_URL)" "$(ASYNCCASSANDRA)"; shift; $(FETCH_DEPENDENCY) + +THIRD_PARTY += $(ASYNCCASSANDRA) \ No newline at end of file diff --git a/third_party/include.mk b/third_party/include.mk index ab92e5b4fe..d7767763b2 100644 --- a/third_party/include.mk +++ b/third_party/include.mk @@ -37,6 +37,13 @@ include third_party/apache/include.mk if BIGTABLE include third_party/alpn-boot/include.mk include third_party/asyncbigtable/include.mk +ASYNCCASSANDRA_VERSION = 0.0 +ASYNCHBASE_VERSION = 0.0 +ZOOKEEPER_VERSION = 0.0 +else +if CASSANDRA +include third_party/asynccassandra/include.mk +ASYNCBIGTABLE_VERSION = 0.0 ASYNCHBASE_VERSION = 0.0 ZOOKEEPER_VERSION = 0.0 else @@ -44,4 +51,6 @@ include third_party/hbase/include.mk include third_party/protobuf/include.mk include third_party/zookeeper/include.mk ASYNCBIGTABLE_VERSION = 0.0 +ASYNCCASSANDRA_VERSION = 0.0 +endif endif \ No newline at end of file From b0a0d194ab7802be9c2f9f72213bbea470c0e451 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Mon, 2 Nov 2015 12:29:44 -0800 Subject: [PATCH 044/233] Fix the Cassandra merge that pulled in a duplicate MUTLIPLY agg. --- src/core/Aggregators.java | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/src/core/Aggregators.java b/src/core/Aggregators.java index 9cc26f5108..ce18e6e560 100644 --- a/src/core/Aggregators.java +++ b/src/core/Aggregators.java @@ -77,11 +77,6 @@ public enum Interpolation { public static final Aggregator MIMMAX = new Max( Interpolation.MIN, "mimmax"); - /** Return the product of two time series - * @since 2.3 */ - public static final Aggregator MULTIPLY = new Multiply( - Interpolation.LERP, "multiply"); - /** Aggregator that returns the number of data points. * WARNING: This currently interpolates with zero-if-missing. In this case * counts will be off when counting multiple time series. Only use this when @@ -155,7 +150,6 @@ public enum Interpolation { aggregators.put("zimsum", ZIMSUM); aggregators.put("mimmin", MIMMIN); aggregators.put("mimmax", MIMMAX); - aggregators.put("multiply", MULTIPLY); PercentileAgg[] percentiles = { p999, p99, p95, p90, p75, p50, @@ -525,29 +519,4 @@ public double runDouble(final Doubles values) { } - private static final class Multiply extends Aggregator { - - public Multiply(final Interpolation method, final String name) { - super(method, name); - } - - @Override - public long runLong(Longs values) { - long result = values.nextLongValue(); - while (values.hasNextValue()) { - result *= values.nextLongValue(); - } - return result; - } - - @Override - public double runDouble(Doubles values) { - double result = values.nextDoubleValue(); - while (values.hasNextValue()) { - double d = values.nextDoubleValue(); - result *= d; - } - return result; - } - } } From e4f3220aa29100503f50a709f48f44fb43d31e23 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Mon, 2 Nov 2015 12:42:34 -0800 Subject: [PATCH 045/233] Add the missing Expression.java class to the makefile. Doh! Signed-off-by: Chris Larsen --- Makefile.am | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile.am b/Makefile.am index 1f078b8338..9a82421d58 100644 --- a/Makefile.am +++ b/Makefile.am @@ -75,6 +75,7 @@ tsdb_SRC := \ src/meta/UIDMeta.java \ src/query/QueryUtil.java \ src/query/expression/Absolute.java \ + src/query/expression/Expression.java \ src/query/expression/ExpressionFactory.java \ src/query/expression/ExpressionReader.java \ src/query/expression/ExpressionTree.java \ From 516ce2ab5df55ab2147d13be34d80890fff44c84 Mon Sep 17 00:00:00 2001 From: Arjun Satish Date: Mon, 2 Nov 2015 11:40:39 -0800 Subject: [PATCH 046/233] Add the Expressions class for parsing an expression into an ExpressionTree. Signed-off-by: Chris Larsen --- src/query/expression/Expressions.java | 100 ++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 src/query/expression/Expressions.java diff --git a/src/query/expression/Expressions.java b/src/query/expression/Expressions.java new file mode 100644 index 0000000000..757f1c3eb0 --- /dev/null +++ b/src/query/expression/Expressions.java @@ -0,0 +1,100 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import java.util.List; + +import com.google.common.base.Preconditions; +import net.opentsdb.core.TSQuery; + +public class Expressions { + + public static ExpressionTree parse(String expr, + List metricQueries, + TSQuery data_query) { + Preconditions.checkNotNull(expr); + if (expr.indexOf('(') == -1 || expr.indexOf(')') == -1) { + throw new RuntimeException("Invalid Expression: " + expr); + } + + ExpressionReader reader = new ExpressionReader(expr.toCharArray()); + reader.skipWhitespaces(); + + String funcName = reader.readFuncName(); + Expression rootExpr = ExpressionFactory.getByName(funcName); + if (rootExpr == null) { + throw new RuntimeException("Could not find evaluator " + + "for function '" + funcName + "'"); + } + + ExpressionTree root = new ExpressionTree(rootExpr, data_query); + + reader.skipWhitespaces(); + if (reader.peek() == '(') { + reader.next(); + parse(reader, metricQueries, root, data_query); + } + + return root; + } + + private static void parse(ExpressionReader reader, List metricQueries, + ExpressionTree root, TSQuery data_query) { + + int parameterIndex = 0; + reader.skipWhitespaces(); + if (reader.peek() != ')') { + String param = reader.readNextParameter(); + parseParam(param, metricQueries, root, data_query, parameterIndex++); + } + + while (true) { + reader.skipWhitespaces(); + if (reader.peek() == ')') { + return; + } else if (reader.isNextSeq(",,")) { + reader.skip(2); //swallow the ",," delimiter + reader.skipWhitespaces(); + String param = reader.readNextParameter(); + parseParam(param, metricQueries, root, data_query, parameterIndex++); + } else { + throw new RuntimeException("Invalid delimiter in parameter " + + "list at pos=" + reader.getMark() + ", expr=" + + reader.toString()); + } + } + } + + private static void parseParam(String param, List metricQueries, + ExpressionTree root, TSQuery data_query, int index) { + if (param == null || param.length() == 0) { + throw new RuntimeException("Invalid Parameter in " + + "Expression"); + } + + if (param.indexOf('(') > 0 && param.indexOf(')') > 0) { + // sub expression + ExpressionTree subTree = parse(param, metricQueries, data_query); + root.addSubExpression(subTree, index); + } else if (param.indexOf(':') >= 0) { + // metric query + metricQueries.add(param); + root.addSubMetricQuery(param, metricQueries.size() - 1, index); + } else { + // expression parameter + root.addFunctionParameter(param); + } + } + +} + From 932db038836b97929e36bbecd986d15c74940623 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Mon, 2 Nov 2015 12:03:12 -0800 Subject: [PATCH 047/233] Cleanup Expressions with comments, formatting and the start of some UTs Signed-off-by: Chris Larsen --- Makefile.am | 2 + src/query/expression/Expressions.java | 169 +++++++++++++-------- test/query/expression/TestExpressions.java | 95 ++++++++++++ 3 files changed, 199 insertions(+), 67 deletions(-) create mode 100644 test/query/expression/TestExpressions.java diff --git a/Makefile.am b/Makefile.am index 9a82421d58..4cbcf80320 100644 --- a/Makefile.am +++ b/Makefile.am @@ -78,6 +78,7 @@ tsdb_SRC := \ src/query/expression/Expression.java \ src/query/expression/ExpressionFactory.java \ src/query/expression/ExpressionReader.java \ + src/query/expression/Expressions.java \ src/query/expression/ExpressionTree.java \ src/query/expression/HighestCurrent.java \ src/query/expression/HighestMax.java \ @@ -241,6 +242,7 @@ test_SRC := \ test/query/expression/TestAbsolute.java \ test/query/expression/TestExpressionFactory.java \ test/query/expression/TestExpressionReader.java \ + test/query/expression/TestExpressions.java \ test/query/expression/TestExpressionTree.java \ test/query/expression/TestHighestCurrent.java \ test/query/expression/TestHighestMax.java \ diff --git a/src/query/expression/Expressions.java b/src/query/expression/Expressions.java index 757f1c3eb0..341134bb05 100644 --- a/src/query/expression/Expressions.java +++ b/src/query/expression/Expressions.java @@ -14,87 +14,122 @@ import java.util.List; -import com.google.common.base.Preconditions; import net.opentsdb.core.TSQuery; +/** + * Static class with helpers to parse and deal with expressions + * @since 2.3 + */ public class Expressions { - public static ExpressionTree parse(String expr, - List metricQueries, - TSQuery data_query) { - Preconditions.checkNotNull(expr); - if (expr.indexOf('(') == -1 || expr.indexOf(')') == -1) { - throw new RuntimeException("Invalid Expression: " + expr); - } - - ExpressionReader reader = new ExpressionReader(expr.toCharArray()); - reader.skipWhitespaces(); - - String funcName = reader.readFuncName(); - Expression rootExpr = ExpressionFactory.getByName(funcName); - if (rootExpr == null) { - throw new RuntimeException("Could not find evaluator " + - "for function '" + funcName + "'"); - } + /** No instantiation for you! */ + private Expressions() { } + + /** + * Parses an expression into a tree + * @param expression The expression to parse (as a string) + * @param metric_queries A list to store the parsed metrics in + * @param data_query The time series query + * @return The parsed tree ready for evaluation + * @throws IllegalArgumentException if the expression was null, empty or + * invalid. + * @throws UnsupportedOperationException if the requested function couldn't + * be found. + */ + public static ExpressionTree parse(final String expression, + final List metric_queries, + final TSQuery data_query) { + if (expression == null || expression.isEmpty()) { + throw new IllegalArgumentException("Expression may not be null or empty"); + } + if (expression.indexOf('(') == -1 || expression.indexOf(')') == -1) { + throw new IllegalArgumentException("Invalid Expression: " + expression); + } - ExpressionTree root = new ExpressionTree(rootExpr, data_query); + final ExpressionReader reader = new ExpressionReader(expression.toCharArray()); + // consume any whitespace ahead of the expression + reader.skipWhitespaces(); - reader.skipWhitespaces(); - if (reader.peek() == '(') { - reader.next(); - parse(reader, metricQueries, root, data_query); - } + final String function_name = reader.readFuncName(); + final Expression root_expression = ExpressionFactory.getByName(function_name); - return root; + final ExpressionTree root = new ExpressionTree(root_expression, data_query); + reader.skipWhitespaces(); + + if (reader.peek() == '(') { + reader.next(); + parse(reader, metric_queries, root, data_query); } - private static void parse(ExpressionReader reader, List metricQueries, - ExpressionTree root, TSQuery data_query) { + return root; + } + + /** + * Helper to parse out the function(s) and parameters + * @param reader The reader used for iterating over the expression + * @param metric_queries A list to store the parsed metrics in + * @param root The root tree + * @param data_query The time series query + */ + private static void parse(final ExpressionReader reader, + final List metric_queries, + final ExpressionTree root, + final TSQuery data_query) { + + int parameter_index = 0; + reader.skipWhitespaces(); + if (reader.peek() != ')') { + final String param = reader.readNextParameter(); + parseParam(param, metric_queries, root, data_query, parameter_index++); + } - int parameterIndex = 0; + while (!reader.isEOF()) { + reader.skipWhitespaces(); + if (reader.peek() == ')') { + return; + } else if (reader.isNextSeq(",,")) { + reader.skip(2); //swallow the ",," delimiter reader.skipWhitespaces(); - if (reader.peek() != ')') { - String param = reader.readNextParameter(); - parseParam(param, metricQueries, root, data_query, parameterIndex++); - } - - while (true) { - reader.skipWhitespaces(); - if (reader.peek() == ')') { - return; - } else if (reader.isNextSeq(",,")) { - reader.skip(2); //swallow the ",," delimiter - reader.skipWhitespaces(); - String param = reader.readNextParameter(); - parseParam(param, metricQueries, root, data_query, parameterIndex++); - } else { - throw new RuntimeException("Invalid delimiter in parameter " + - "list at pos=" + reader.getMark() + ", expr=" - + reader.toString()); - } - } + final String param = reader.readNextParameter(); + parseParam(param, metric_queries, root, data_query, parameter_index++); + } else { + throw new IllegalArgumentException("Invalid delimiter in parameter " + + "list at pos=" + reader.getMark() + ", expr=" + + reader.toString()); + } + } + } + + /** + * Helper that parses out the parameter from the expression + * @param param The parameter to parse + * @param metric_queries A list to store the parsed metrics in + * @param root The root tree + * @param data_query The time series query + * @param index Index of the parameter + */ + private static void parseParam(final String param, + final List metric_queries, + final ExpressionTree root, + final TSQuery data_query, + final int index) { + if (param == null || param.length() == 0) { + throw new IllegalArgumentException("Parameter cannot be null or empty"); } - private static void parseParam(String param, List metricQueries, - ExpressionTree root, TSQuery data_query, int index) { - if (param == null || param.length() == 0) { - throw new RuntimeException("Invalid Parameter in " + - "Expression"); - } - - if (param.indexOf('(') > 0 && param.indexOf(')') > 0) { - // sub expression - ExpressionTree subTree = parse(param, metricQueries, data_query); - root.addSubExpression(subTree, index); - } else if (param.indexOf(':') >= 0) { - // metric query - metricQueries.add(param); - root.addSubMetricQuery(param, metricQueries.size() - 1, index); - } else { - // expression parameter - root.addFunctionParameter(param); - } + if (param.indexOf('(') > 0 && param.indexOf(')') > 0) { + // sub expression + final ExpressionTree sub_tree = parse(param, metric_queries, data_query); + root.addSubExpression(sub_tree, index); + } else if (param.indexOf(':') >= 0) { + // metric query + metric_queries.add(param); + root.addSubMetricQuery(param, metric_queries.size() - 1, index); + } else { + // expression parameter + root.addFunctionParameter(param); } + } } diff --git a/test/query/expression/TestExpressions.java b/test/query/expression/TestExpressions.java new file mode 100644 index 0000000000..92f6ea4fc1 --- /dev/null +++ b/test/query/expression/TestExpressions.java @@ -0,0 +1,95 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; + +import java.util.ArrayList; +import java.util.List; + +import net.opentsdb.core.TSQuery; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +@RunWith(PowerMockRunner.class) +@PowerMockIgnore({"javax.management.*", "javax.xml.*", + "ch.qos.*", "org.slf4j.*", + "com.sum.*", "org.xml.*"}) +@PrepareForTest({ TSQuery.class }) +public class TestExpressions { + private TSQuery data_query; + private List metric_queries; + + @Before + public void before() throws Exception { + data_query = mock(TSQuery.class); + metric_queries = new ArrayList(); + } + + @Test + public void parse() throws Exception { + final ExpressionTree tree = Expressions.parse( + "scale(sys.cpu)", metric_queries, data_query); + assertEquals("scale()", tree.toString()); + } + + @Test + public void parseWithWhitespace() throws Exception { + final ExpressionTree tree = Expressions.parse( + " scale(sys.cpu)", metric_queries, data_query); + assertEquals("scale()", tree.toString()); + } + + @Test (expected = IllegalArgumentException.class) + public void parseNullExpression() throws Exception { + Expressions.parse(null, metric_queries, data_query); + } + + @Test (expected = IllegalArgumentException.class) + public void parseEmptyExpression() throws Exception { + Expressions.parse("", metric_queries, data_query); + } + + @Test (expected = IllegalArgumentException.class) + public void parseMissingOpenParens() throws Exception { + Expressions.parse("scalesys.cpu)", metric_queries, data_query); + } + + @Test (expected = IllegalArgumentException.class) + public void parseMissingClosingParens() throws Exception { + Expressions.parse("scale(sys.cpu", metric_queries, data_query); + } + + // TODO - These two may be problematic and need validation/fixing? + @Test + public void parseNullMetricQueries() throws Exception { + final ExpressionTree tree = Expressions.parse( + "scale(sys.cpu)", null, data_query); + assertEquals("scale()", tree.toString()); + } + + @Test + public void parseNullTSQuery() throws Exception { + final ExpressionTree tree = Expressions.parse( + "scale(sys.cpu)", metric_queries, null); + assertEquals("scale()", tree.toString()); + } + + //TODO - Need to add more tests around parsing nested functions and params +} From acfe754cde001dc06f79d9f6f32a39d4e277fc1b Mon Sep 17 00:00:00 2001 From: Arjun Satish Date: Mon, 2 Nov 2015 12:15:50 -0800 Subject: [PATCH 048/233] Some more tests around function parsing for the Expressions Signed-off-by: Chris Larsen --- test/query/expression/TestExpressions.java | 52 ++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/test/query/expression/TestExpressions.java b/test/query/expression/TestExpressions.java index 92f6ea4fc1..60dd6ec7c1 100644 --- a/test/query/expression/TestExpressions.java +++ b/test/query/expression/TestExpressions.java @@ -13,11 +13,13 @@ package net.opentsdb.query.expression; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.mockito.Mockito.mock; import java.util.ArrayList; import java.util.List; +import net.opentsdb.core.DataPoints; import net.opentsdb.core.TSQuery; import org.junit.Before; @@ -40,6 +42,7 @@ public class TestExpressions { public void before() throws Exception { data_query = mock(TSQuery.class); metric_queries = new ArrayList(); + ExpressionFactory.addFunction("foo", new FooExpression()); } @Test @@ -55,6 +58,40 @@ public void parseWithWhitespace() throws Exception { " scale(sys.cpu)", metric_queries, data_query); assertEquals("scale()", tree.toString()); } + + @Test + public void parseMultiParameter() { + final String expr = "foo(sum:proc.sys.cpu,, sum:proc.meminfo.memfree)"; + final ExpressionTree tree = Expressions.parse(expr, metric_queries, null); + assertEquals("foo(proc.sys.cpu,proc.meminfo.memfree)", tree.toString()); + assertEquals(2, metric_queries.size()); + assertEquals("sum:proc.sys.cpu", metric_queries.get(0)); + assertEquals("sum:proc.meminfo.memfree", metric_queries.get(1)); + assertNull(tree.funcParams()); + } + + @Test + public void parseNestedExpr() { + final String expr = "foo(sum:proc.sys.cpu,, foo(sum:proc.a.b))"; + final ExpressionTree tree = Expressions.parse(expr, metric_queries, null); + assertEquals("foo(foo(proc.a.b),proc.sys.cpu)", tree.toString()); + assertEquals(2, metric_queries.size()); + assertEquals("sum:proc.sys.cpu", metric_queries.get(0)); + assertEquals("sum:proc.a.b", metric_queries.get(1)); + assertNull(tree.funcParams()); + } + + @Test + public void parseExprWithParam() { + final String expr = "foo(sum:proc.sys.cpu,, 100,, 3.1415)"; + final ExpressionTree tree = Expressions.parse(expr, metric_queries, null); + assertEquals("foo(proc.sys.cpu)", tree.toString()); + assertEquals(1, metric_queries.size()); + assertEquals("sum:proc.sys.cpu", metric_queries.get(0)); + assertEquals(2, tree.funcParams().size()); + assertEquals("100", tree.funcParams().get(0)); + assertEquals("3.1415", tree.funcParams().get(1)); + } @Test (expected = IllegalArgumentException.class) public void parseNullExpression() throws Exception { @@ -92,4 +129,19 @@ public void parseNullTSQuery() throws Exception { } //TODO - Need to add more tests around parsing nested functions and params + + /** Dummy test expression implementation */ + private static class FooExpression implements Expression { + @Override + public DataPoints[] evaluate(final TSQuery data_query, + final List query_results, final List params) { + return new DataPoints[0]; + } + + @Override + public String writeStringField(final List query_params, + final String inner_expressions) { + return "foo(" + inner_expressions + ")"; + } + } } From 8c74b89b34686fe0186caba3dfca35a99fba67ad Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Tue, 3 Nov 2015 11:43:52 -0800 Subject: [PATCH 049/233] Add some really ugly makefile code to compile the expression parser source files and add the source and classes to the TSDB jar. TODO - clean this up! It's not pretty but it seems to function and it doesn't cleanup after itself yet. Signed-off-by: Chris Larsen --- .gitignore | 2 +- Makefile.am | 26 +++++++--- src/parser.jj | 68 +++++++++++++++++++++++++ third_party/include.mk | 1 + third_party/javacc/include.mk | 23 +++++++++ third_party/javacc/javacc-6.1.2.jar.md5 | 1 + 6 files changed, 113 insertions(+), 8 deletions(-) create mode 100644 src/parser.jj create mode 100644 third_party/javacc/include.mk create mode 100644 third_party/javacc/javacc-6.1.2.jar.md5 diff --git a/.gitignore b/.gitignore index 24afdb80df..41729cc410 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,4 @@ guava-rpm-maker/\.project src-main src-test plugin_test.jar -/bin/ +bin/ diff --git a/Makefile.am b/Makefile.am index 4cbcf80320..7ab7eb5924 100644 --- a/Makefile.am +++ b/Makefile.am @@ -171,6 +171,7 @@ tsdb_DEPS = \ $(JACKSON_ANNOTATIONS) \ $(JACKSON_CORE) \ $(JACKSON_DATABIND) \ + $(JAVACC) \ $(NETTY) \ $(SLF4J_API) \ $(SUASYNC) \ @@ -194,7 +195,7 @@ else tsdb_DEPS += \ $(ASYNCHBASE) \ $(PROTOBUF) \ - $(ZOOKEEPER) + $(ZOOKEEPER) maven_profile_bigtable := false maven_profile_hbase := true maven_profile_cassandra := false @@ -329,11 +330,11 @@ test_DEPS = \ $(tsdb_DEPS) \ $(JAVASSIST) \ $(JUNIT) \ - $(HAMCREST) \ + $(HAMCREST) \ $(MOCKITO) \ - $(OBJENESIS) \ + $(OBJENESIS) \ $(POWERMOCK_MOCKITO) \ - $(jar) + $(jar) httpui_SRC := \ src/tsd/client/DateTimeBox.java \ @@ -347,6 +348,11 @@ httpui_SRC := \ httpui_DEPS = src/tsd/QueryUi.gwt.xml +# TODO(CL) - There is likely a MUCH better way to compile and add the expression sources and jars. +expr_package = net/opentsdb/query/expression/parser +expr_src_dir = $(abs_builddir)/src/$(expr_package) +get_expr_classes = `classes=''; for f in $(packagedir)$(expr_package)/*.class; do classes="$$classes $$f"; done; echo $$classes;` + #dist_pkgdata_DATA = src/logback.xml dist_static_DATA = src/tsd/static/favicon.ico @@ -405,7 +411,7 @@ install-exec-hook: $(builddata_SRC): .git/HEAD $(tsdb_SRC) $(top_srcdir)/build-aux/gen_build_data.sh $(srcdir)/build-aux/gen_build_data.sh $(builddata_SRC) $(package).$(builddata_subpackage) $(PACKAGE_VERSION) -jar: $(jar) .javac-unittests-stamp .gwtc-stamp +jar: runjavacc $(jar) .javac-unittests-stamp .gwtc-stamp JAVA_COMPILE := $(JAVAC) $(AM_JAVACFLAGS) -d . @@ -420,6 +426,9 @@ filter_src = \ src="$$src $$i";; \ esac; \ done; \ + for f in $(expr_src_dir)/*.java; do \ + src="$$src $$f"; \ + done; \ test -n "$$src" || exit 0 # Touches all the targets if any of the dependencies are newer. # This is useful to force-recompile all files if one of the @@ -438,7 +447,7 @@ find_jar = test -f "$$jar" && echo "$$jar" || echo "$(srcdir)/$$jar" get_dep_classpath = `for jar in $(tsdb_DEPS); do $(find_jar); done | tr '\n' ':'` .javac-stamp: $(tsdb_SRC) $(builddata_SRC) @$(filter_src); cp=$(get_dep_classpath); \ - echo "$(JAVA_COMPILE) -cp $$cp $$src"; \ + echo "DO THA COMPILE!!! $(JAVA_COMPILE) -cp $$cp $$src"; \ $(JAVA_COMPILE) -cp $$cp $$src @touch "$@" @@ -653,7 +662,7 @@ manifest: .javac-stamp .git/HEAD echo "Implementation-Vendor: $(spec_vendor)"; } >"$@" $(jar): manifest .javac-stamp $(classes) - $(JAR) cfm `basename $(jar)` manifest $(classes_with_nested_classes) \ + $(JAR) cfm `basename $(jar)` manifest $(classes_with_nested_classes) $(get_expr_classes) \ || { rv=$$? && rm -f `basename $(jar)` && exit $$rv; } # ^^^^^^^^^^^^^^^^^^^^^^^ # I've seen cases where `jar' exits with an error but leaves a partially built .jar file! @@ -677,6 +686,9 @@ $(JAVADOC_DIR)/index.html: $(tsdb_SRC) -link $(JDK_JAVADOC) -link $(NETTY_JAVADOC) -link $(SUASYNC_JAVADOC) \ $? $(builddata_SRC) +runjavacc: + $(JAVA) -cp $(JAVACC) javacc -STATIC:false -LOOKAHEAD:5 -OUTPUT_DIRECTORY:$(expr_src_dir) $(abs_srcdir)/src/parser.jj; echo PWD: `pwd`; + dist-hook: $(mkdir_p) $(distdir)/.git echo $(git_version) >$(distdir)/.git/HEAD diff --git a/src/parser.jj b/src/parser.jj new file mode 100644 index 0000000000..8517b29f07 --- /dev/null +++ b/src/parser.jj @@ -0,0 +1,68 @@ +PARSER_BEGIN(SyntaxChecker) +package net.opentsdb.query.expression.parser; + +import net.opentsdb.query.expression.ExpressionTree; +import net.opentsdb.core.TSQuery; + +import java.util.List; +import com.google.common.base.Joiner; +import com.google.common.collect.Lists; + +/** +* A simple class for validating the expressions +* @since 2.3 +*/ +public class SyntaxChecker { + + private TSQuery data_query; + private List metricQueries; + + public void setTSQuery(TSQuery data_query) { + this.data_query = data_query; + } + + public void setMetricQueries(List metricQueries) { + this.metricQueries = metricQueries; + } + + public static void main(String[] args) { + try { + new SyntaxChecker(new java.io.StringReader(args[0])).EXPRESSION(); + System.out.println("Syntax is okay"); + } catch (Throwable e) { + // Catching Throwable is ugly but JavaCC throws Error objects! + System.out.println("Syntax check failed: " + e.getMessage()); + } + } +} + +PARSER_END(SyntaxChecker) + +SKIP: { " " | "\t" | "\n" | "\r" } +TOKEN: { } +TOKEN: { } + +ExpressionTree EXPRESSION(): {Token name; int paramIndex=0;} { + name= { ExpressionTree tree=new ExpressionTree(name.image,data_query); } + "(" PARAMETER(tree, paramIndex++) ("," PARAMETER(tree, paramIndex++))* ")" + {return tree;} +} + +void PARAMETER(ExpressionTree tree, int paramIndex): {String metric; Token param; ExpressionTree subTree;} { + subTree=EXPRESSION() {tree.addSubExpression(subTree, paramIndex);} | + metric=METRIC() {metricQueries.add(metric); tree.addSubMetricQuery(metric, metricQueries.size()-1, paramIndex);} | + param= {tree.addFunctionParameter(param.image);} +} + +// metric is agg:[interval-agg:][rate:]metric[{tag=value,...}] +String METRIC() : {Token agg,itvl,rate,metric,tagk,tagv; StringBuilder builder = new StringBuilder(); + Joiner JOINER = Joiner.on(",").skipNulls(); + List tagPairs = Lists.newArrayList(); + } { + agg = ":" { builder.append(agg.image).append(":"); } + (itvl= ":" { builder.append(itvl.image).append(":"); })? + (rate= ":" { builder.append(rate.image).append(":"); })? + metric= { builder.append(metric.image); } + ("{" tagk= "=" tagv= {tagPairs.add(tagk+"="+tagv);} + ("," tagk= "=" tagv= {tagPairs.add(tagk+"="+tagv);})* "}")? + {if (tagPairs.size() > 0) builder.append("{").append(JOINER.join(tagPairs)).append("}"); return builder.toString();} } \ No newline at end of file diff --git a/third_party/include.mk b/third_party/include.mk index d7767763b2..3a25a241a9 100644 --- a/third_party/include.mk +++ b/third_party/include.mk @@ -22,6 +22,7 @@ include third_party/guava/include.mk include third_party/gwt/include.mk include third_party/hamcrest/include.mk include third_party/jackson/include.mk +include third_party/javacc/include.mk include third_party/javassist/include.mk include third_party/junit/include.mk include third_party/logback/include.mk diff --git a/third_party/javacc/include.mk b/third_party/javacc/include.mk new file mode 100644 index 0000000000..2c7f29785a --- /dev/null +++ b/third_party/javacc/include.mk @@ -0,0 +1,23 @@ +# Copyright (C) 2015 The OpenTSDB Authors. +# +# This library is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 2.1 of the License, or +# (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . + +JAVACC_VERSION := 6.1.2 +JAVACC := third_party/javacc/javacc-$(JAVACC_VERSION).jar +JAVACC_BASE_URL := http://central.maven.org/maven2/net/java/dev/javacc/javacc/$(JAVACC_VERSION) + +$(JAVACC): $(JAVACC).md5 + set dummy "$(JAVACC_BASE_URL)" "$(JAVACC)"; shift; $(FETCH_DEPENDENCY) + +THIRD_PARTY += $(JAVACC) diff --git a/third_party/javacc/javacc-6.1.2.jar.md5 b/third_party/javacc/javacc-6.1.2.jar.md5 new file mode 100644 index 0000000000..84d9e28cfc --- /dev/null +++ b/third_party/javacc/javacc-6.1.2.jar.md5 @@ -0,0 +1 @@ +c74b2df75b4c46209d6da22bf4dad976 From d9c61a5db4a1fc0f92597c63a77b5d5868cf8471 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Tue, 3 Nov 2015 12:27:57 -0800 Subject: [PATCH 050/233] Move the parser options into the .jj file and add a javacc compliation plugin to the pom. Signed-off-by: Chris Larsen --- Makefile.am | 2 +- pom.xml.in | 22 ++++++++++++++++++++++ src/parser.jj | 6 ++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index 7ab7eb5924..acc7c609fa 100644 --- a/Makefile.am +++ b/Makefile.am @@ -687,7 +687,7 @@ $(JAVADOC_DIR)/index.html: $(tsdb_SRC) $? $(builddata_SRC) runjavacc: - $(JAVA) -cp $(JAVACC) javacc -STATIC:false -LOOKAHEAD:5 -OUTPUT_DIRECTORY:$(expr_src_dir) $(abs_srcdir)/src/parser.jj; echo PWD: `pwd`; + $(JAVA) -cp $(JAVACC) javacc -OUTPUT_DIRECTORY:$(expr_src_dir) $(abs_srcdir)/src/parser.jj; echo PWD: `pwd`; dist-hook: $(mkdir_p) $(distdir)/.git diff --git a/pom.xml.in b/pom.xml.in index cf68ef34e6..8e37d22217 100644 --- a/pom.xml.in +++ b/pom.xml.in @@ -314,6 +314,28 @@ + + com.helger.maven + ph-javacc-maven-plugin + 2.8.0 + + + jjc + generate-sources + + javacc + + + 1.6 + true + net.opentsdb.query.expression.parser + ${basedir}/src/ + ${project.build.directory}/generated-sources/ + + + + + diff --git a/src/parser.jj b/src/parser.jj index 8517b29f07..29b00c9e40 100644 --- a/src/parser.jj +++ b/src/parser.jj @@ -1,3 +1,9 @@ +/** Options required by Maven */ +options { + STATIC = false; + LOOKAHEAD = 5; +} + PARSER_BEGIN(SyntaxChecker) package net.opentsdb.query.expression.parser; From 94b7e60feb174b5fa6d0773d9c0540aa75ce6c58 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Tue, 3 Nov 2015 15:39:59 -0800 Subject: [PATCH 051/233] Add the /api/query/gexp endpoint for an experimental URI endpoint to handle the Graphite style expressions. Thanks to Arjun Satish and Turn for the code. More tests, functions and support in the built in GUI to come. Signed-off-by: Chris Larsen --- src/query/expression/Expressions.java | 31 +++++++++ src/tsd/QueryRpc.java | 61 +++++++++++++++-- test/tsd/TestQueryRpc.java | 96 ++++++++++++++++++--------- 3 files changed, 152 insertions(+), 36 deletions(-) diff --git a/src/query/expression/Expressions.java b/src/query/expression/Expressions.java index 341134bb05..1d7e2de954 100644 --- a/src/query/expression/Expressions.java +++ b/src/query/expression/Expressions.java @@ -12,9 +12,13 @@ // see . package net.opentsdb.query.expression; +import java.io.StringReader; +import java.util.ArrayList; import java.util.List; import net.opentsdb.core.TSQuery; +import net.opentsdb.query.expression.parser.ParseException; +import net.opentsdb.query.expression.parser.SyntaxChecker; /** * Static class with helpers to parse and deal with expressions @@ -64,6 +68,33 @@ public static ExpressionTree parse(final String expression, return root; } + /** + * Parses a list of string expressions into the proper trees, adding the + * metrics to the {@link metric_queries} list. + * @param expressions A list of zero or more expressions (if empty, you get an + * empty tree list back) + * @param ts_query The original query with timestamps + * @param metric_queries The list to fill with metrics to fetch + */ + public static List parseExpressions( + final List expressions, + final TSQuery ts_query, + final List metric_queries) { + final List trees = + new ArrayList(expressions.size()); + for (final String expr: expressions) { + final SyntaxChecker checker = new SyntaxChecker(new StringReader(expr)); + checker.setMetricQueries(metric_queries); + checker.setTSQuery(ts_query); + try { + trees.add(checker.EXPRESSION()); + } catch (ParseException e) { + throw new IllegalArgumentException("Failed to parse " + expr, e); + } + } + return trees; + } + /** * Helper to parse out the function(s) and parameters * @param reader The reader used for iterating over the expression diff --git a/src/tsd/QueryRpc.java b/src/tsd/QueryRpc.java index 58f1ae38fb..4b5ab81a7b 100644 --- a/src/tsd/QueryRpc.java +++ b/src/tsd/QueryRpc.java @@ -42,6 +42,8 @@ import net.opentsdb.core.Tags; import net.opentsdb.meta.Annotation; import net.opentsdb.meta.TSUIDQuery; +import net.opentsdb.query.expression.ExpressionTree; +import net.opentsdb.query.expression.Expressions; import net.opentsdb.query.filter.TagVFilter; import net.opentsdb.stats.QueryStats; import net.opentsdb.uid.NoSuchUniqueName; @@ -90,8 +92,10 @@ public void execute(final TSDB tsdb, final HttpQuery query) if (endpoint.toLowerCase().equals("last")) { handleLastDataPointQuery(tsdb, query); + } else if (endpoint.toLowerCase().equals("gexp")){ + handleQuery(tsdb, query, true); } else { - handleQuery(tsdb, query); + handleQuery(tsdb, query, false); } } @@ -99,10 +103,14 @@ public void execute(final TSDB tsdb, final HttpQuery query) * Processing for a data point query * @param tsdb The TSDB to which we belong * @param query The HTTP query to parse/respond + * @param allow_expressions Whether or not expressions should be parsed + * (based on the endpoint) */ - private void handleQuery(final TSDB tsdb, final HttpQuery query) { + private void handleQuery(final TSDB tsdb, final HttpQuery query, + final boolean allow_expressions) { final long start = DateTime.currentTimeMillis(); final TSQuery data_query; + final List expressions; if (query.method() == HttpMethod.POST) { switch (query.apiVersion()) { case 0: @@ -114,8 +122,10 @@ private void handleQuery(final TSDB tsdb, final HttpQuery query) { "Requested API version not implemented", "Version " + query.apiVersion() + " is not implemented"); } + expressions = null; } else { - data_query = this.parseQuery(tsdb, query); + expressions = new ArrayList(); + data_query = this.parseQuery(tsdb, query, expressions); } if (query.getAPIMethod() == HttpMethod.DELETE && @@ -192,8 +202,20 @@ public Object call(final Exception e) throws Exception { class QueriesCB implements Callback> { public Object call(final ArrayList query_results) throws Exception { - results.addAll(query_results); - + if (allow_expressions) { + // process each of the expressions into a new list, then merge it + // with the original. This avoids possible recursion loops. + final List expression_results = + new ArrayList(expressions.size()); + // let exceptions bubble up + for (final ExpressionTree expression : expressions) { + expression_results.add(expression.evaluate(query_results)); + } + results.addAll(expression_results); + } else { + results.addAll(query_results); + } + /** Simply returns the buffer once serialization is complete and logs it */ class SendIt implements Callback { public Object call(final ChannelBuffer buffer) throws Exception { @@ -432,10 +454,13 @@ public String toString() { * Parses a query string legacy style query from the URI * @param tsdb The TSDB we belong to * @param query The HTTP Query for parsing + * @param expressions A list of parsed expression trees filled from the URI. + * If this is null, it means any expressions in the URI will be skipped. * @return A TSQuery if parsing was successful * @throws BadRequestException if parsing was unsuccessful */ - private TSQuery parseQuery(final TSDB tsdb, final HttpQuery query) { + private TSQuery parseQuery(final TSDB tsdb, final HttpQuery query, + final List expressions) { final TSQuery data_query = new TSQuery(); data_query.setStart(query.getRequiredQueryStringParam("start")); @@ -488,6 +513,30 @@ private TSQuery parseQuery(final TSDB tsdb, final HttpQuery query) { } } + // TODO - testing out the graphite style expressions here with the "exp" + // param that could stand for experimental or expression ;) + if (expressions != null) { + if (query.hasQueryStringParam("exp")) { + final List uri_expressions = query.getQueryStringParams("exp"); + final List metric_queries = new ArrayList( + uri_expressions.size()); + // parse the expressions into their trees. If one or more expressions + // are improper then it will toss an exception up + expressions.addAll(Expressions.parseExpressions( + uri_expressions, data_query, metric_queries)); + // iterate over each of the parsed metric queries and store it in the + // TSQuery list so that we fetch the data for them. + for (final String mq: metric_queries) { + parseMTypeSubQuery(mq, data_query); + } + } + } else { + if (LOG.isDebugEnabled()) { + LOG.debug("Received a request with an expression but at the " + + "wrong endpoint: " + query); + } + } + if (data_query.getQueries() == null || data_query.getQueries().size() < 1) { throw new BadRequestException("Missing sub queries"); } diff --git a/test/tsd/TestQueryRpc.java b/test/tsd/TestQueryRpc.java index d498b963ff..cce155c244 100644 --- a/test/tsd/TestQueryRpc.java +++ b/test/tsd/TestQueryRpc.java @@ -23,12 +23,14 @@ import java.lang.reflect.Method; import java.nio.charset.Charset; +import java.util.List; import net.opentsdb.core.DataPoints; import net.opentsdb.core.Query; import net.opentsdb.core.TSDB; import net.opentsdb.core.TSQuery; import net.opentsdb.core.TSSubQuery; +import net.opentsdb.query.expression.ExpressionTree; import net.opentsdb.query.filter.TagVLiteralOrFilter; import net.opentsdb.query.filter.TagVRegexFilter; import net.opentsdb.query.filter.TagVWildcardFilter; @@ -62,12 +64,13 @@ public final class TestQueryRpc { private QueryRpc rpc; private Query empty_query = mock(Query.class); private Query query_result; + private List expressions; private static final Method parseQuery; static { try { parseQuery = QueryRpc.class.getDeclaredMethod("parseQuery", - TSDB.class, HttpQuery.class); + TSDB.class, HttpQuery.class, List.class); parseQuery.setAccessible(true); } catch (Exception e) { throw new RuntimeException("Failed in static initializer", e); @@ -80,6 +83,7 @@ public void before() throws Exception { empty_query = mock(Query.class); query_result = mock(Query.class); rpc = new QueryRpc(); + expressions = null; when(tsdb.newQuery()).thenReturn(query_result); when(empty_query.run()).thenReturn(new DataPoints[0]); @@ -93,7 +97,7 @@ public void before() throws Exception { public void parseQueryMType() throws Exception { HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?start=1h-ago&m=sum:sys.cpu.0"); - TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query); + TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query, expressions); assertNotNull(tsq); assertEquals("1h-ago", tsq.getStart()); assertNotNull(tsq.getQueries()); @@ -107,7 +111,7 @@ public void parseQueryMType() throws Exception { public void parseQueryMTypeWEnd() throws Exception { HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?start=1h-ago&end=5m-ago&m=sum:sys.cpu.0"); - TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query); + TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query, expressions); assertEquals("5m-ago", tsq.getEnd()); } @@ -115,7 +119,7 @@ public void parseQueryMTypeWEnd() throws Exception { public void parseQuery2MType() throws Exception { HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?start=1h-ago&m=sum:sys.cpu.0&m=avg:sys.cpu.1"); - TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query); + TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query, expressions); assertNotNull(tsq.getQueries()); assertEquals(2, tsq.getQueries().size()); TSSubQuery sub1 = tsq.getQueries().get(0); @@ -132,7 +136,7 @@ public void parseQuery2MType() throws Exception { public void parseQueryMTypeWRate() throws Exception { HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?start=1h-ago&m=sum:rate:sys.cpu.0"); - TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query); + TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query, expressions); TSSubQuery sub = tsq.getQueries().get(0); assertTrue(sub.getRate()); } @@ -141,7 +145,7 @@ public void parseQueryMTypeWRate() throws Exception { public void parseQueryMTypeWDS() throws Exception { HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?start=1h-ago&m=sum:1h-avg:sys.cpu.0"); - TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query); + TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query, expressions); TSSubQuery sub = tsq.getQueries().get(0); assertEquals("1h-avg", sub.getDownsample()); } @@ -150,7 +154,7 @@ public void parseQueryMTypeWDS() throws Exception { public void parseQueryMTypeWDSAndFill() throws Exception { HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?start=1h-ago&m=sum:1h-avg-lerp:sys.cpu.0"); - TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query); + TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query, expressions); TSSubQuery sub = tsq.getQueries().get(0); assertEquals("1h-avg-lerp", sub.getDownsample()); } @@ -159,7 +163,7 @@ public void parseQueryMTypeWDSAndFill() throws Exception { public void parseQueryMTypeWRateAndDS() throws Exception { HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?start=1h-ago&m=sum:1h-avg:rate:sys.cpu.0"); - TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query); + TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query, expressions); TSSubQuery sub = tsq.getQueries().get(0); assertTrue(sub.getRate()); assertEquals("1h-avg", sub.getDownsample()); @@ -169,7 +173,7 @@ public void parseQueryMTypeWRateAndDS() throws Exception { public void parseQueryMTypeWTag() throws Exception { HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?start=1h-ago&m=sum:sys.cpu.0{host=web01}"); - TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query); + TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query, expressions); TSSubQuery sub = tsq.getQueries().get(0); assertNotNull(sub.getTags()); assertEquals("literal_or(web01)", sub.getTags().get("host")); @@ -180,7 +184,7 @@ public void parseQueryMTypeWGroupByRegex() throws Exception { HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?start=1h-ago&m=sum:sys.cpu.0{host=" + TagVRegexFilter.FILTER_NAME + "(something(foo|bar))}"); - TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query); + TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query, expressions); TSSubQuery sub = tsq.getQueries().get(0); sub.validateAndSetQuery(); assertEquals(1, sub.getFilters().size()); @@ -192,7 +196,7 @@ public void parseQueryMTypeWGroupByWildcardExplicit() throws Exception { HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?start=1h-ago&m=sum:sys.cpu.0{host=" + TagVWildcardFilter.FILTER_NAME + "(*quirm)}"); - TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query); + TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query, expressions); TSSubQuery sub = tsq.getQueries().get(0); sub.validateAndSetQuery(); assertEquals(1, sub.getFilters().size()); @@ -203,7 +207,7 @@ public void parseQueryMTypeWGroupByWildcardExplicit() throws Exception { public void parseQueryMTypeWGroupByWildcardImplicit() throws Exception { HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?start=1h-ago&m=sum:sys.cpu.0{host=*quirm}"); - TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query); + TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query, expressions); TSSubQuery sub = tsq.getQueries().get(0); sub.validateAndSetQuery(); assertEquals(1, sub.getFilters().size()); @@ -214,7 +218,7 @@ public void parseQueryMTypeWGroupByWildcardImplicit() throws Exception { public void parseQueryMTypeWWildcardFilterExplicit() throws Exception { HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?start=1h-ago&m=sum:sys.cpu.0{}{host=wildcard(*quirm)}"); - TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query); + TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query, expressions); TSSubQuery sub = tsq.getQueries().get(0); sub.validateAndSetQuery(); assertEquals(1, sub.getFilters().size()); @@ -225,7 +229,7 @@ public void parseQueryMTypeWWildcardFilterExplicit() throws Exception { public void parseQueryMTypeWWildcardFilterImplicit() throws Exception { HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?start=1h-ago&m=sum:sys.cpu.0{}{host=*quirm}"); - TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query); + TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query, expressions); TSSubQuery sub = tsq.getQueries().get(0); sub.validateAndSetQuery(); assertEquals(1, sub.getFilters().size()); @@ -236,7 +240,7 @@ public void parseQueryMTypeWWildcardFilterImplicit() throws Exception { public void parseQueryMTypeWGroupByAndWildcardFilterExplicit() throws Exception { HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?start=1h-ago&m=sum:sys.cpu.0{colo=lga}{host=wildcard(*quirm)}"); - TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query); + TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query, expressions); TSSubQuery sub = tsq.getQueries().get(0); sub.validateAndSetQuery(); assertTrue(sub.getFilters().get(0) instanceof TagVWildcardFilter); @@ -248,7 +252,7 @@ public void parseQueryMTypeWGroupByAndWildcardFilterSameTagK() throws Exception HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?start=1h-ago&m=sum:sys.cpu.0{host=quirm|tsort}" + "{host=wildcard(*quirm)}"); - TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query); + TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query, expressions); TSSubQuery sub = tsq.getQueries().get(0); sub.validateAndSetQuery(); assertTrue(sub.getFilters().get(0) instanceof TagVWildcardFilter); @@ -261,7 +265,7 @@ public void parseQueryMTypeWGroupByFilterAndWildcardFilterSameTagK() HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?start=1h-ago&m=sum:sys.cpu.0{host=wildcard(*tsort)}" + "{host=wildcard(*quirm)}"); - TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query); + TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query, expressions); TSSubQuery sub = tsq.getQueries().get(0); sub.validateAndSetQuery(); assertEquals(2, sub.getFilters().size()); @@ -274,7 +278,7 @@ public void parseQueryMTypeWGroupByFilterMissingClose() throws Exception { HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?start=1h-ago&m=sum:sys.cpu.0{host=wildcard(*tsort)}" + "{host=wildcard(*quirm)"); - parseQuery.invoke(rpc, tsdb, query); + parseQuery.invoke(rpc, tsdb, query, expressions); } @Test (expected = IllegalArgumentException.class) @@ -282,7 +286,7 @@ public void parseQueryMTypeWGroupByFilterMissingEquals() throws Exception { HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?start=1h-ago&m=sum:sys.cpu.0{host=wildcard(*tsort)}" + "{hostwildcard(*quirm)}"); - parseQuery.invoke(rpc, tsdb, query); + parseQuery.invoke(rpc, tsdb, query, expressions); } @Test (expected = IllegalArgumentException.class) @@ -290,14 +294,14 @@ public void parseQueryMTypeWGroupByNoSuchFilter() throws Exception { HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?start=1h-ago&m=sum:sys.cpu.0{host=nosuchfilter(*tsort)}" + "{host=dummyfilter(*quirm)}"); - parseQuery.invoke(rpc, tsdb, query); + parseQuery.invoke(rpc, tsdb, query, expressions); } @Test public void parseQueryMTypeWEmptyFilterBrackets() throws Exception { HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?start=1h-ago&m=sum:sys.cpu.0{}{}"); - TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query); + TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query, expressions); TSSubQuery sub = tsq.getQueries().get(0); sub.validateAndSetQuery(); assertEquals(0, sub.getFilters().size()); @@ -307,7 +311,7 @@ public void parseQueryMTypeWEmptyFilterBrackets() throws Exception { public void parseQueryTSUIDType() throws Exception { HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?start=1h-ago&tsuid=sum:010101"); - TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query); + TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query, expressions); assertNotNull(tsq); assertEquals("1h-ago", tsq.getStart()); assertNotNull(tsq.getQueries()); @@ -322,7 +326,7 @@ public void parseQueryTSUIDType() throws Exception { public void parseQueryTSUIDTypeMulti() throws Exception { HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?start=1h-ago&tsuid=sum:010101,020202"); - TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query); + TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query, expressions); assertNotNull(tsq); assertEquals("1h-ago", tsq.getStart()); assertNotNull(tsq.getQueries()); @@ -338,7 +342,7 @@ public void parseQueryTSUIDTypeMulti() throws Exception { public void parseQuery2TSUIDType() throws Exception { HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?start=1h-ago&tsuid=sum:010101&tsuid=avg:020202"); - TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query); + TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query, expressions); assertNotNull(tsq); assertEquals("1h-ago", tsq.getStart()); assertNotNull(tsq.getQueries()); @@ -359,7 +363,7 @@ public void parseQuery2TSUIDType() throws Exception { public void parseQueryTSUIDTypeWRate() throws Exception { HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?start=1h-ago&tsuid=sum:rate:010101"); - TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query); + TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query, expressions); assertNotNull(tsq); assertEquals("1h-ago", tsq.getStart()); assertNotNull(tsq.getQueries()); @@ -375,7 +379,7 @@ public void parseQueryTSUIDTypeWRate() throws Exception { public void parseQueryTSUIDTypeWDS() throws Exception { HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?start=1h-ago&tsuid=sum:1m-sum:010101"); - TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query); + TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query, expressions); assertNotNull(tsq); assertEquals("1h-ago", tsq.getStart()); assertNotNull(tsq.getQueries()); @@ -391,7 +395,7 @@ public void parseQueryTSUIDTypeWDS() throws Exception { public void parseQueryTSUIDTypeWRateAndDS() throws Exception { HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?start=1h-ago&tsuid=sum:1m-sum:rate:010101"); - TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query); + TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query, expressions); assertNotNull(tsq); assertEquals("1h-ago", tsq.getStart()); assertNotNull(tsq.getQueries()); @@ -408,7 +412,7 @@ public void parseQueryTSUIDTypeWRateAndDS() throws Exception { public void parseQueryWPadding() throws Exception { HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?start=1h-ago&m=sum:sys.cpu.0&padding"); - TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query); + TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query, expressions); assertNotNull(tsq); assertTrue(tsq.getPadding()); } @@ -417,14 +421,14 @@ public void parseQueryWPadding() throws Exception { public void parseQueryStartMissing() throws Exception { HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?end=1h-ago&m=sum:sys.cpu.0"); - parseQuery.invoke(rpc, tsdb, query); + parseQuery.invoke(rpc, tsdb, query, expressions); } @Test (expected = BadRequestException.class) public void parseQueryNoSubQuery() throws Exception { HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?start=1h-ago"); - parseQuery.invoke(rpc, tsdb, query); + parseQuery.invoke(rpc, tsdb, query, expressions); } @Test @@ -532,5 +536,37 @@ public void deleteDatapointsBadRequest() throws Exception { assertTrue(json.contains("Deleting data is not enabled")); } + @Test + public void gexp() throws Exception { + final DataPoints[] datapoints = new DataPoints[1]; + datapoints[0] = new MockDataPoints().getMock(); + when(query_result.runAsync()).thenReturn( + Deferred.fromResult(datapoints)); + + final HttpQuery query = NettyMocks.getQuery(tsdb, + "/api/query/gexp?start=1h-ago&exp=scale(sum:sys.cpu.user,1)"); + rpc.execute(tsdb, query); + assertEquals(query.response().getStatus(), HttpResponseStatus.OK); + final String json = + query.response().getContent().toString(Charset.forName("UTF-8")); + assertTrue(json.contains("\"metric\":\"system.cpu.user\"")); + } + + @Test + public void gexpBadExpression() throws Exception { + final DataPoints[] datapoints = new DataPoints[1]; + datapoints[0] = new MockDataPoints().getMock(); + when(query_result.runAsync()).thenReturn( + Deferred.fromResult(datapoints)); + + final HttpQuery query = NettyMocks.getQuery(tsdb, + "/api/query/gexp?start=1h-ago&exp=scale(sum:sys.cpu.user,notanumber)"); + rpc.execute(tsdb, query); + assertEquals(query.response().getStatus(), HttpResponseStatus.BAD_REQUEST); + final String json = + query.response().getContent().toString(Charset.forName("UTF-8")); + assertTrue(json.contains("factor")); + } + //TODO(cl) add unit tests for the rate options parsing } \ No newline at end of file From 58ca3721c16b562d2e7564ad17204244bb30a460 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Tue, 3 Nov 2015 17:18:15 -0800 Subject: [PATCH 052/233] Fix #574 by pulling the metrics randomization setting from the TSD config during CLI UID assignment. Signed-off-by: Chris Larsen --- src/tools/UidManager.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/tools/UidManager.java b/src/tools/UidManager.java index 2885179754..fc51dd6287 100644 --- a/src/tools/UidManager.java +++ b/src/tools/UidManager.java @@ -40,6 +40,7 @@ import net.opentsdb.uid.NoSuchUniqueId; import net.opentsdb.uid.NoSuchUniqueName; import net.opentsdb.uid.UniqueId; +import net.opentsdb.uid.UniqueId.UniqueIdType; import net.opentsdb.utils.Config; /** @@ -158,7 +159,7 @@ private static int runCommand(final TSDB tsdb, usage("Wrong number of arguments"); return 2; } - return assign(tsdb.getClient(), table, idwidth, args); + return assign(tsdb, table, idwidth, args); } else if (args[0].equals("rename")) { if (nargs != 4) { usage("Wrong number of arguments"); @@ -349,22 +350,27 @@ private static boolean printResult(final ArrayList row, /** * Implements the {@code assign} subcommand. - * @param client The HBase client to use. + * @param tsdb The TSDB to use. * @param table The name of the HBase table to use. * @param idwidth Number of bytes on which the UIDs should be. * @param args Command line arguments ({@code assign name [names]}). * @return The exit status of the command (0 means success). */ - private static int assign(final HBaseClient client, + private static int assign(final TSDB tsdb, final byte[] table, final short idwidth, final String[] args) { - final UniqueId uid = new UniqueId(client, table, args[1], (int) idwidth); + boolean randomize = false; + if (UniqueIdType.valueOf(args[1]) == UniqueIdType.METRIC) { + randomize = tsdb.getConfig().getBoolean("tsd.core.uid.random_metrics"); + } + final UniqueId uid = new UniqueId(tsdb.getClient(), table, args[1], + (int) idwidth, randomize); for (int i = 2; i < args.length; i++) { try { uid.getOrCreateId(args[i]); // Lookup again the ID we've just created and print it. - extactLookupName(client, table, idwidth, args[1], args[i]); + extactLookupName(tsdb.getClient(), table, idwidth, args[1], args[i]); } catch (HBaseException e) { LOG.error("error while processing " + args[i], e); return 3; From ce4660d643cc269da25d79f609b45b56b52bd896 Mon Sep 17 00:00:00 2001 From: Yubao Liu Date: Tue, 27 Oct 2015 15:48:46 +0800 Subject: [PATCH 053/233] make OOM handler be able to handle multiple instances of opentsdb server Signed-off-by: Chris Larsen --- tools/opentsdb_restart.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/opentsdb_restart.py b/tools/opentsdb_restart.py index eaad7537f6..31425750a8 100644 --- a/tools/opentsdb_restart.py +++ b/tools/opentsdb_restart.py @@ -8,8 +8,11 @@ import os import subprocess +service_name = "opentsdb" +if 'NAME' in os.environ: + service_name = os.environ['NAME'] -subprocess.call(["service", "opentsdb", "stop"]) +subprocess.call(["service", service_name, "stop"]) # Close any file handles we inherited from our parent JVM. We need # to do this before restarting so that the socket isn't held open. openfiles = [int(f) for f in os.listdir("/proc/self/fd")] @@ -17,4 +20,4 @@ # that there is less chance of errors with those standard streams. # Other files start at fd 3. os.closerange(3, max(openfiles)) -subprocess.call(["service", "opentsdb", "start"]) +subprocess.call(["service", service_name, "start"]) From cc41502925769bd573c7650069301162ba17e40e Mon Sep 17 00:00:00 2001 From: Hong Dai Thanh Date: Fri, 30 Oct 2015 16:37:46 +0700 Subject: [PATCH 054/233] Fix typo TUSID --> TSUID Signed-off-by: Chris Larsen --- src/core/Query.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/Query.java b/src/core/Query.java index 519e36ebf5..34553c591e 100644 --- a/src/core/Query.java +++ b/src/core/Query.java @@ -119,7 +119,7 @@ void setTimeSeries(String metric, Map tags, * to run asynchronously and use different scanners, we can allow different * TSUIDs. * Note: This method will not check to determine if the TSUIDs are - * valid, since that wastes time and we *assume* that the user provides TUSIDs + * valid, since that wastes time and we *assume* that the user provides TSUIDs * that are up to date. * @param tsuids A list of one or more TSUIDs to scan for * @param function The aggregation function to use on results @@ -139,7 +139,7 @@ public void setTimeSeries(final List tsuids, * to run asynchronously and use different scanners, we can allow different * TSUIDs. * Note: This method will not check to determine if the TSUIDs are - * valid, since that wastes time and we *assume* that the user provides TUSIDs + * valid, since that wastes time and we *assume* that the user provides TSUIDs * that are up to date. * @param tsuids A list of one or more TSUIDs to scan for * @param function The aggregation function to use on results From 825c49eba1ad4d24d728773ff41a8f7f01f182bd Mon Sep 17 00:00:00 2001 From: louyl Date: Fri, 9 Oct 2015 17:38:44 +0800 Subject: [PATCH 055/233] Fix up Makefile.am Signed-off-by: Chris Larsen --- Makefile.am | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile.am b/Makefile.am index acc7c609fa..f2549c02c3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -502,8 +502,8 @@ install-data-local: staticroot install-data-lib install-data-tools \ install-data-bin install-data-etc @$(NORMAL_INSTALL) test -z "$(staticdir)" || $(mkdir_p) "$(DESTDIR)$(staticdir)" - @set -e; pwd; ls -lFh; cd "$(DEV_TSD_STATICROOT)"; \ - list=`find -L . ! -type d`; for p in $$list; do \ + @set -e; pwd; ls -lFh; (cd "$(DEV_TSD_STATICROOT)"; \ + list=`find -L . ! -type d`); for p in $$list; do \ p=$${p#./}; \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ dstdir=`dirname "$(DESTDIR)$(staticdir)/$$p"`; \ From 926e6538ee8726f8e0e15b06b0f0d44ac7b1571c Mon Sep 17 00:00:00 2001 From: Hari Krishna Dara Date: Wed, 7 Oct 2015 17:25:44 +0530 Subject: [PATCH 056/233] Bug fixes for failing unit tests when UID width is changed from the default 3 (specifically tested with 4) The following tests failed as of 2.1.0: TestTimeSeriesLookup.tagkOnlyMeta:211 expected:<5> but was:<6> TestTimeSeriesLookup.tagkOnlyMetaStar:227 expected:<5> but was:<6> TestTimeSeriesLookup.tagkOnlyData:244 expected:<5> but was:<6> TestTimeSeriesLookup.tagkOnly2Meta:260 expected:<2> but was:<0> TestUniqueIdRpc.tsuidPostByM Signed-off-by: Chris Larsen --- src/search/TimeSeriesLookup.java | 8 ++++---- src/tsd/UniqueIdRpc.java | 5 +++-- src/uid/.UniqueId.java.swo | Bin 0 -> 16384 bytes 3 files changed, 7 insertions(+), 6 deletions(-) create mode 100644 src/uid/.UniqueId.java.swo diff --git a/src/search/TimeSeriesLookup.java b/src/search/TimeSeriesLookup.java index d640644c04..ff66760b0f 100644 --- a/src/search/TimeSeriesLookup.java +++ b/src/search/TimeSeriesLookup.java @@ -498,13 +498,13 @@ private String getRowKeyRegex() { } // catch any left over tagk/tag pairs - if (index < pairs.size()){ + if (index < pairs.size()){ // This condition is true whenever the first tagk in the pairs has a null value. buf.setLength(0); buf.append("(?s)^.{").append(query.useMeta() ? TSDB.metrics_width() : TSDB.metrics_width() + Const.SALT_WIDTH()) .append("}"); if (!query.useMeta()) { - buf.append("(?:.{").append(Const.TIMESTAMP_BYTES).append("})*"); + buf.append("(?:.{").append(Const.TIMESTAMP_BYTES).append("})"); } ByteArrayPair last_pair = null; @@ -525,7 +525,7 @@ private String getRowKeyRegex() { buf.append(")"); } // moving on to the next tagk set - buf.append("(?:.{6})*"); // catch tag pairs in between + buf.append("(?:.{").append(tagsize).append("})*"); // catch tag pairs in between buf.append("(?:"); if (pairs.get(index).getKey() != null && pairs.get(index).getValue() != null) { @@ -535,7 +535,7 @@ private String getRowKeyRegex() { } else { buf.append("\\Q"); QueryUtil.addId(buf, pairs.get(index).getKey(), true); - buf.append("(?:.{").append(value_width).append("})+"); + buf.append("(?:.{").append(value_width).append("})"); } } last_pair = pairs.get(index); diff --git a/src/tsd/UniqueIdRpc.java b/src/tsd/UniqueIdRpc.java index 1747e3f188..5f49d0af6c 100644 --- a/src/tsd/UniqueIdRpc.java +++ b/src/tsd/UniqueIdRpc.java @@ -573,8 +573,9 @@ private String getTSUIDForMetric(final String query_string, TSDB tsdb) { try { buf.write(tsdb.getUID(UniqueIdType.METRIC, metric)); for (Entry e: sortedTags.entrySet()) { - buf.write(tsdb.getUID(UniqueIdType.TAGK, e.getKey()), 0, 3); - buf.write(tsdb.getUID(UniqueIdType.TAGV, e.getValue()), 0, 3); + // Fix for net.opentsdb.tsd.TestUniqueIdRpc.tsuidPostByM() + buf.write(tsdb.getUID(UniqueIdType.TAGK, e.getKey()), 0, TSDB.tagk_width()); + buf.write(tsdb.getUID(UniqueIdType.TAGV, e.getValue()), 0, TSDB.tagv_width()); } } catch (IOException e) { throw new BadRequestException(e); diff --git a/src/uid/.UniqueId.java.swo b/src/uid/.UniqueId.java.swo new file mode 100644 index 0000000000000000000000000000000000000000..20195dc9de84f769e58b2aed50952f2f6a08cb11 GIT binary patch literal 16384 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 fu}^YGPbBs9C09u Date: Wed, 4 Nov 2015 13:53:14 -0800 Subject: [PATCH 057/233] Add the ByteSet utility class for storing unique byte arrays. Signed-off-by: Chris Larsen --- Makefile.am | 2 + src/utils/ByteSet.java | 110 ++++++++++++++++++++++++++++++++++++ test/utils/TestByteSet.java | 71 +++++++++++++++++++++++ 3 files changed, 183 insertions(+) create mode 100644 src/utils/ByteSet.java create mode 100644 test/utils/TestByteSet.java diff --git a/Makefile.am b/Makefile.am index f2549c02c3..1b07e4c556 100644 --- a/Makefile.am +++ b/Makefile.am @@ -153,6 +153,7 @@ tsdb_SRC := \ src/uid/UniqueId.java \ src/uid/UniqueIdInterface.java \ src/utils/ByteArrayPair.java \ + src/utils/ByteSet.java \ src/utils/Config.java \ src/utils/DateTime.java \ src/utils/Exceptions.java \ @@ -296,6 +297,7 @@ test_SRC := \ test/uid/TestRandomUniqueId.java \ test/uid/TestUniqueId.java \ test/utils/TestByteArrayPair.java \ + test/utils/TestByteSet.java \ test/utils/TestConfig.java \ test/utils/TestDateTime.java \ test/utils/TestExceptions.java \ diff --git a/src/utils/ByteSet.java b/src/utils/ByteSet.java new file mode 100644 index 0000000000..6a1bad1d41 --- /dev/null +++ b/src/utils/ByteSet.java @@ -0,0 +1,110 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.utils; + +import java.util.AbstractSet; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Set; + +import org.hbase.async.Bytes.ByteMap; + +/** + * An implementation of a set based on the AsyncHBase ByteMap. This provides + * a unique set implementation of byte arrays, matching on the contents of + * the arrays, not on the hash codes. + */ +public class ByteSet extends AbstractSet + implements Set, Cloneable, java.io.Serializable { + + private static final long serialVersionUID = -496061795957902656L; + + // Dummy value to associate with an Object in the backing Map + private static final Object PRESENT = new Object(); + + private transient ByteMap map; + + /** + * Instantiates a unique set of byte arrays based on the array contents. + */ + public ByteSet() { + map = new ByteMap(); + } + + @Override + public Iterator iterator() { + return map.keySet().iterator(); + } + + @Override + public int size() { + return map.size(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public boolean contains(final Object key) { + return map.containsKey(key); + } + + @Override + public boolean add(final byte[] key) { + return map.put(key, PRESENT) == null; + } + + @Override + public boolean remove(final Object key) { + return map.remove(key) == PRESENT; + } + + @Override + public void clear() { + map.clear(); + } + + @Override + public ByteSet clone() { + try { + ByteSet new_set = (ByteSet) super.clone(); + new_set.map = (ByteMap) map.clone(); + return new_set; + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } + + @Override + public String toString() { + final Iterator it = map.keySet().iterator(); + if (!it.hasNext()) { + return "[]"; + } + + final StringBuilder buf = new StringBuilder(); + buf.append('['); + for (;;) { + final byte[] array = it.next(); + buf.append(Arrays.toString(array)); + if (!it.hasNext()) { + return buf.append(']').toString(); + } + buf.append(','); + } + } + + // TODO - writeObject, readObject +} diff --git a/test/utils/TestByteSet.java b/test/utils/TestByteSet.java new file mode 100644 index 0000000000..5cd83a240c --- /dev/null +++ b/test/utils/TestByteSet.java @@ -0,0 +1,71 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.utils; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.Iterator; + +import org.junit.Test; + +public class TestByteSet { + + private static final byte[] V1 = new byte[] { 0, 0, 1 }; + private static final byte[] V2 = new byte[] { 0, 0, 2 }; + private static final byte[] V3 = new byte[] { 0, 0, 3 }; + private static final byte[] V4 = new byte[] { 0, 0, 4 }; + + @Test + public void ctor() { + final ByteSet set = new ByteSet(); + assertNotNull(set); + assertEquals(0, set.size()); + assertTrue(set.isEmpty()); + } + + @Test + public void goodOperations() { + final ByteSet set = new ByteSet(); + set.add(V3); + set.add(V2); + set.add(V1); + + assertEquals(3, set.size()); + assertFalse(set.isEmpty()); + + // should come out in order + final Iterator it = set.iterator(); + assertArrayEquals(V1, it.next()); + assertArrayEquals(V2, it.next()); + assertArrayEquals(V3, it.next()); + assertFalse(it.hasNext()); + + assertEquals("[[0, 0, 1],[0, 0, 2],[0, 0, 3]]", set.toString()); + + assertTrue(set.contains(V1)); + assertFalse(set.contains(V4)); + + assertTrue(set.remove(V1)); + assertFalse(set.contains(V1)); + assertFalse(set.remove(V4)); + + set.clear(); + assertFalse(set.contains(V2)); + assertFalse(set.contains(V3)); + assertTrue(set.isEmpty()); + } +} From b93e54981cf9c669d886b19fc00409efa305e962 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Wed, 4 Nov 2015 14:38:47 -0800 Subject: [PATCH 058/233] Add two methods to get the metric and aggregated tag UIDs from the DataPoints interface implementers. Signed-off-by: Chris Larsen --- src/core/BatchedDataPoints.java | 15 ++- src/core/DataPoints.java | 13 ++ src/core/IncomingDataPoints.java | 15 ++- src/core/RowSeq.java | 11 ++ src/core/Span.java | 11 ++ src/core/SpanGroup.java | 22 ++++ .../expression/PostAggregatedDataPoints.java | 16 +++ test/core/TestRowSeq.java | 114 ++++++++++++++++- test/core/TestSpan.java | 119 ++++++++++++++++++ test/core/TestSpanGroup.java | 71 ++++++++++- 10 files changed, 398 insertions(+), 9 deletions(-) diff --git a/src/core/BatchedDataPoints.java b/src/core/BatchedDataPoints.java index c56143f234..e399802345 100644 --- a/src/core/BatchedDataPoints.java +++ b/src/core/BatchedDataPoints.java @@ -39,8 +39,8 @@ final class BatchedDataPoints implements WritableDataPoints { private final TSDB tsdb; /** - * The row key. 3 bytes for the metric name, 4 bytes for the base timestamp, - * 6 bytes per tag (3 for the name, 3 for the value). + * The row key. Optional salt + 3 bytes for the metric name, 4 bytes for the + * base timestamp, 6 bytes per tag (3 for the name, 3 for the value). */ private byte[] row_key; @@ -309,6 +309,12 @@ public Deferred metricNameAsync() { return tsdb.metrics.getNameAsync(id); } + @Override + public byte[] metricUID() { + return Arrays.copyOfRange(row_key, Const.SALT_WIDTH(), + Const.SALT_WIDTH() + TSDB.metrics_width()); + } + @Override public Map getTags() { try { @@ -343,6 +349,11 @@ public Deferred> getAggregatedTagsAsync() { return Deferred.fromResult(empty); } + @Override + public List getAggregatedTagUids() { + return Collections.emptyList(); + } + @Override public List getTSUIDs() { return Collections.emptyList(); diff --git a/src/core/DataPoints.java b/src/core/DataPoints.java index cef3c69790..c9a4930992 100644 --- a/src/core/DataPoints.java +++ b/src/core/DataPoints.java @@ -38,6 +38,12 @@ public interface DataPoints extends Iterable { * @since 1.2 */ Deferred metricNameAsync(); + + /** + * @return the metric UID + * @since 2.3 + */ + byte[] metricUID(); /** * Returns the tags associated with these data points. @@ -93,6 +99,13 @@ public interface DataPoints extends Iterable { */ Deferred> getAggregatedTagsAsync(); + /** + * Returns the tagk UIDs associated with some but not all of the data points. + * @return a non-{@code null} list of tagk UIDs. + * @since 2.3 + */ + List getAggregatedTagUids(); + /** * Returns a list of unique TSUIDs contained in the results * @return an empty list if there were no results, otherwise a list of TSUIDs diff --git a/src/core/IncomingDataPoints.java b/src/core/IncomingDataPoints.java index 20ca6d51ac..4c98e3a596 100644 --- a/src/core/IncomingDataPoints.java +++ b/src/core/IncomingDataPoints.java @@ -49,8 +49,8 @@ final class IncomingDataPoints implements WritableDataPoints { private final TSDB tsdb; /** - * The row key. 3 bytes for the metric name, 4 bytes for the base timestamp, 6 - * bytes per tag (3 for the name, 3 for the value). + * The row key. Optional salt + 3 bytes for the metric name, 4 bytes for + * the base timestamp, 6 bytes per tag (3 for the name, 3 for the value). */ private byte[] row; @@ -422,6 +422,12 @@ public Deferred metricNameAsync() { return tsdb.metrics.getNameAsync(id); } + @Override + public byte[] metricUID() { + return Arrays.copyOfRange(row, Const.SALT_WIDTH(), + Const.SALT_WIDTH() + TSDB.metrics_width()); + } + public Map getTags() { try { return getTagsAsync().joinUninterruptibly(); @@ -450,6 +456,11 @@ public Deferred> getAggregatedTagsAsync() { return Deferred.fromResult(empty); } + @Override + public List getAggregatedTagUids() { + return Collections.emptyList(); + } + public List getTSUIDs() { return Collections.emptyList(); } diff --git a/src/core/RowSeq.java b/src/core/RowSeq.java index 42194aab3b..dcf8d608eb 100644 --- a/src/core/RowSeq.java +++ b/src/core/RowSeq.java @@ -285,6 +285,12 @@ public Deferred metricNameAsync() { return RowKey.metricNameAsync(tsdb, key); } + @Override + public byte[] metricUID() { + return Arrays.copyOfRange(key, Const.SALT_WIDTH(), + Const.SALT_WIDTH() + TSDB.metrics_width()); + } + public Map getTags() { try { return getTagsAsync().joinUninterruptibly(); @@ -314,6 +320,11 @@ public Deferred> getAggregatedTagsAsync() { return Deferred.fromResult(empty); } + @Override + public List getAggregatedTagUids() { + return Collections.emptyList(); + } + public List getTSUIDs() { return Collections.emptyList(); } diff --git a/src/core/Span.java b/src/core/Span.java index c49ee773cb..c9f31ba77f 100644 --- a/src/core/Span.java +++ b/src/core/Span.java @@ -86,6 +86,12 @@ public Deferred metricNameAsync() { return rows.get(0).metricNameAsync(); } + @Override + public byte[] metricUID() { + checkNotEmpty(); + return rows.get(0).metricUID(); + } + /** * @return the list of tag pairs for the rows in this span * @throws IllegalStateException if the span was empty @@ -121,6 +127,11 @@ public Deferred> getAggregatedTagsAsync() { final List empty = Collections.emptyList(); return Deferred.fromResult(empty); } + + @Override + public List getAggregatedTagUids() { + return Collections.emptyList(); + } /** @return the number of data points in this span, O(n) * Unfortunately we must walk the entire array for every row as there may be a diff --git a/src/core/SpanGroup.java b/src/core/SpanGroup.java index 026cb0fedc..17625699ef 100644 --- a/src/core/SpanGroup.java +++ b/src/core/SpanGroup.java @@ -13,6 +13,7 @@ package net.opentsdb.core; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -327,6 +328,11 @@ public Deferred metricNameAsync() { spans.get(0).metricNameAsync(); } + @Override + public byte[] metricUID() { + return spans.isEmpty() ? new byte[] {} : spans.get(0).metricUID(); + } + public Map getTags() { try { return getTagsAsync().joinUninterruptibly(); @@ -388,6 +394,22 @@ public Deferred> getAggregatedTagsAsync() { return resolveAggTags(aggregated_tag_uids); } + + @Override + public List getAggregatedTagUids() { + if (aggregated_tag_uids != null) { + return new ArrayList(aggregated_tag_uids); + } + + if (spans.isEmpty()) { + return Collections.emptyList(); + } + + if (aggregated_tag_uids == null) { + computeTags(); + } + return new ArrayList(aggregated_tag_uids); + } public List getTSUIDs() { List tsuids = new ArrayList(spans.size()); diff --git a/src/query/expression/PostAggregatedDataPoints.java b/src/query/expression/PostAggregatedDataPoints.java index 872def9c49..e0787f62fd 100644 --- a/src/query/expression/PostAggregatedDataPoints.java +++ b/src/query/expression/PostAggregatedDataPoints.java @@ -75,6 +75,14 @@ public Deferred metricNameAsync() { } return base_data_points.metricNameAsync(); } + + @Override + public byte[] metricUID() { + if (alias != null) { + return new byte[] { }; + } + return base_data_points.metricUID(); + } @Override public Map getTags() { @@ -109,6 +117,14 @@ public Deferred> getAggregatedTagsAsync() { return base_data_points.getAggregatedTagsAsync(); } + @Override + public List getAggregatedTagUids() { + if (alias != null) { + return Collections.emptyList(); + } + return base_data_points.getAggregatedTagUids(); + } + @Override public List getTSUIDs() { return base_data_points.getTSUIDs(); diff --git a/test/core/TestRowSeq.java b/test/core/TestRowSeq.java index db9eb1b913..1a3792759b 100644 --- a/test/core/TestRowSeq.java +++ b/test/core/TestRowSeq.java @@ -12,6 +12,7 @@ // see . package net.opentsdb.core; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -27,6 +28,7 @@ import org.hbase.async.Bytes; import org.hbase.async.KeyValue; +import org.hbase.async.Bytes.ByteMap; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -814,8 +816,118 @@ public void seekMsPastLastDp() throws Exception { it.next(); } + @Test + public void metricUID() throws Exception { + final byte[] qual1 = { 0x00, 0x07 }; + final byte[] val1 = Bytes.fromLong(4L); + final byte[] qual2 = { 0x00, 0x27 }; + final byte[] val2 = Bytes.fromLong(5L); + final byte[] qual12 = MockBase.concatByteArrays(qual1, qual2); + final KeyValue kv = makekv(qual12, + MockBase.concatByteArrays(val1, val2, ZERO)); + + final RowSeq rs = new RowSeq(tsdb); + rs.setRow(kv); + + assertArrayEquals(new byte[] { 0, 0, 1 }, rs.metricUID()); + } + + @Test + public void metricUIDSalted() throws Exception { + setupSalt(); + final byte[] qual1 = { 0x00, 0x07 }; + final byte[] val1 = Bytes.fromLong(4L); + final byte[] qual2 = { 0x00, 0x27 }; + final byte[] val2 = Bytes.fromLong(5L); + final byte[] qual12 = MockBase.concatByteArrays(qual1, qual2); + final KeyValue kv = makekv(qual12, + MockBase.concatByteArrays(val1, val2, ZERO)); + + final RowSeq rs = new RowSeq(tsdb); + rs.setRow(kv); + + assertArrayEquals(new byte[] { 0, 0, 1 }, rs.metricUID()); + } + + @Test (expected = NullPointerException.class) + public void metricUIDKeyNotSet() throws Exception { + final RowSeq rs = new RowSeq(tsdb); + rs.metricUID(); + } + + @Test + public void getTagUids() throws Exception { + final byte[] qual1 = { 0x00, 0x07 }; + final byte[] val1 = Bytes.fromLong(4L); + final byte[] qual2 = { 0x00, 0x27 }; + final byte[] val2 = Bytes.fromLong(5L); + final byte[] qual12 = MockBase.concatByteArrays(qual1, qual2); + final KeyValue kv = makekv(qual12, + MockBase.concatByteArrays(val1, val2, ZERO)); + + final RowSeq rs = new RowSeq(tsdb); + rs.setRow(kv); + + final ByteMap uids = rs.getTagUids(); + assertEquals(1, uids.size()); + assertArrayEquals(new byte[] { 0, 0, 1 }, uids.firstKey()); + assertArrayEquals(new byte[] { 0, 0, 2 }, + uids.firstEntry().getValue()); + } + + @Test + public void getTagUidsSalted() throws Exception { + setupSalt(); + final byte[] qual1 = { 0x00, 0x07 }; + final byte[] val1 = Bytes.fromLong(4L); + final byte[] qual2 = { 0x00, 0x27 }; + final byte[] val2 = Bytes.fromLong(5L); + final byte[] qual12 = MockBase.concatByteArrays(qual1, qual2); + final KeyValue kv = makekv(qual12, + MockBase.concatByteArrays(val1, val2, ZERO)); + + final RowSeq rs = new RowSeq(tsdb); + rs.setRow(kv); + + final ByteMap uids = rs.getTagUids(); + assertEquals(1, uids.size()); + assertEquals(0, Bytes.memcmp(new byte[] { 0, 0, 1 }, uids.firstKey())); + assertEquals(0, Bytes.memcmp(new byte[] { 0, 0, 2 }, + uids.firstEntry().getValue())); + } + + @Test (expected = NullPointerException.class) + public void getTagUidsNotSet() throws Exception { + final RowSeq rs = new RowSeq(tsdb); + rs.getTagUids(); + } + + @Test + public void getAggregatedTagUids() throws Exception { + final byte[] qual1 = { 0x00, 0x07 }; + final byte[] val1 = Bytes.fromLong(4L); + final byte[] qual2 = { 0x00, 0x27 }; + final byte[] val2 = Bytes.fromLong(5L); + final byte[] qual12 = MockBase.concatByteArrays(qual1, qual2); + final KeyValue kv = makekv(qual12, + MockBase.concatByteArrays(val1, val2, ZERO)); + + final RowSeq rs = new RowSeq(tsdb); + rs.setRow(kv); + + assertEquals(0, rs.getAggregatedTagUids().size()); + } + + /** Shorthand to create a {@link KeyValue}. */ + public static KeyValue makekv(final byte[] qualifier, final byte[] value) { + if (Const.SALT_WIDTH() > 0) { + return new KeyValue(SALTED_KEY, FAMILY, qualifier, value); + } + return new KeyValue(KEY, FAMILY, qualifier, value); + } + /** Shorthand to create a {@link KeyValue}. */ - private static KeyValue makekv(final byte[] key, final byte[] qualifier, + public static KeyValue makekv(final byte[] key, final byte[] qualifier, final byte[] value) { return new KeyValue(key, FAMILY, qualifier, value); } diff --git a/test/core/TestSpan.java b/test/core/TestSpan.java index dbfc247ce5..b9c1b668f3 100644 --- a/test/core/TestSpan.java +++ b/test/core/TestSpan.java @@ -12,8 +12,10 @@ // see . package net.opentsdb.core; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.when; import static org.powermock.api.mockito.PowerMockito.mock; @@ -25,6 +27,7 @@ import org.hbase.async.Bytes; import org.hbase.async.KeyValue; +import org.hbase.async.Bytes.ByteMap; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -408,4 +411,120 @@ public void lastTimestampInRowMs() throws Exception { assertEquals(1356998400008L, Span.lastTimestampInRow((short) 3, kv)); } + + @Test + public void metricUID() throws Exception { + final byte[] qual1 = { 0x00, 0x07 }; + final byte[] val1 = Bytes.fromLong(4L); + final byte[] qual2 = { 0x00, 0x27 }; + final byte[] val2 = Bytes.fromLong(5L); + final byte[] qual12 = MockBase.concatByteArrays(qual1, qual2); + + final Span span = new Span(tsdb); + span.addRow(new KeyValue(HOUR1, FAMILY, qual12, + MockBase.concatByteArrays(val1, val2, ZERO))); + + assertEquals(2, span.size()); + + assertArrayEquals(new byte[] { 0, 0, 1 }, span.metricUID()); + } + + @Test + public void metricUIDSalted() throws Exception { + PowerMockito.mockStatic(Const.class); + PowerMockito.when(Const.SALT_WIDTH()).thenReturn(1); + PowerMockito.when(Const.SALT_BUCKETS()).thenReturn(2); + + final byte[] qual1 = { 0x00, 0x07 }; + final byte[] val1 = Bytes.fromLong(4L); + final byte[] qual2 = { 0x00, 0x27 }; + final byte[] val2 = Bytes.fromLong(5L); + final byte[] qual12 = MockBase.concatByteArrays(qual1, qual2); + + final Span span = new Span(tsdb); + final byte[] key = new byte[HOUR1.length + 1]; + System.arraycopy(HOUR1, 0, key, 1, HOUR1.length); + span.addRow(new KeyValue(key, FAMILY, qual12, + MockBase.concatByteArrays(val1, val2, ZERO))); + + assertEquals(2, span.size()); + + assertArrayEquals(new byte[] { 0, 0, 1 }, span.metricUID()); + } + + @Test + public void getTagUids() { + final byte[] qual1 = { 0x00, 0x07 }; + final byte[] val1 = Bytes.fromLong(4L); + final byte[] qual2 = { 0x00, 0x27 }; + final byte[] val2 = Bytes.fromLong(5L); + final byte[] qual12 = MockBase.concatByteArrays(qual1, qual2); + + final Span span = new Span(tsdb); + span.addRow(new KeyValue(HOUR1, FAMILY, qual12, + MockBase.concatByteArrays(val1, val2, ZERO))); + + assertEquals(2, span.size()); + final ByteMap uids = span.getTagUids(); + assertEquals(1, uids.size()); + assertArrayEquals(new byte[] { 0, 0, 1 }, uids.firstKey()); + assertArrayEquals(new byte[] { 0, 0, 2 }, + uids.firstEntry().getValue()); + } + + @Test + public void getTagUidsSalted() { + PowerMockito.mockStatic(Const.class); + PowerMockito.when(Const.SALT_WIDTH()).thenReturn(1); + PowerMockito.when(Const.SALT_BUCKETS()).thenReturn(2); + + final byte[] qual1 = { 0x00, 0x07 }; + final byte[] val1 = Bytes.fromLong(4L); + final byte[] qual2 = { 0x00, 0x27 }; + final byte[] val2 = Bytes.fromLong(5L); + final byte[] qual12 = MockBase.concatByteArrays(qual1, qual2); + + final Span span = new Span(tsdb); + final byte[] key = new byte[HOUR1.length + 1]; + System.arraycopy(HOUR1, 0, key, 1, HOUR1.length); + span.addRow(new KeyValue(key, FAMILY, qual12, + MockBase.concatByteArrays(val1, val2, ZERO))); + + assertEquals(2, span.size()); + final ByteMap uids = span.getTagUids(); + assertEquals(1, uids.size()); + assertArrayEquals(new byte[] { 0, 0, 1 }, uids.firstKey()); + assertArrayEquals(new byte[] { 0, 0, 2 }, + uids.firstEntry().getValue()); + } + + @Test (expected = IllegalStateException.class) + public void getTagUidsNotSet() { + final Span span = new Span(tsdb); + span.getTagUids(); + } + + @Test + public void getAggregatedTagUids() { + final byte[] qual1 = { 0x00, 0x07 }; + final byte[] val1 = Bytes.fromLong(4L); + final byte[] qual2 = { 0x00, 0x27 }; + final byte[] val2 = Bytes.fromLong(5L); + final byte[] qual12 = MockBase.concatByteArrays(qual1, qual2); + + final Span span = new Span(tsdb); + span.addRow(new KeyValue(HOUR1, FAMILY, qual12, + MockBase.concatByteArrays(val1, val2, ZERO))); + + assertEquals(2, span.size()); + final List uids = span.getAggregatedTagUids(); + assertEquals(0, uids.size()); + } + + @Test + public void getAggregatedTagUidsNotSet() { + final Span span = new Span(tsdb); + assertTrue(span.getAggregatedTagUids().isEmpty()); + } + } diff --git a/test/core/TestSpanGroup.java b/test/core/TestSpanGroup.java index 8b0d9d848e..f24458c4c9 100644 --- a/test/core/TestSpanGroup.java +++ b/test/core/TestSpanGroup.java @@ -12,15 +12,16 @@ // see . package net.opentsdb.core; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.ArrayList; +import java.util.List; import net.opentsdb.utils.Config; -import org.hbase.async.Bytes; import org.hbase.async.Bytes.ByteMap; import org.hbase.async.HBaseClient; import org.junit.Before; @@ -45,6 +46,19 @@ public void before() { tsdb = PowerMockito.mock(TSDB.class); } + @Test + public void metricUID() throws Exception { + final Span span = mock(Span.class); + when(span.metricUID()).thenReturn(new byte[] { 0, 0, 1 }); + + final SpanGroup group = PowerMockito.spy(new SpanGroup(tsdb, start_ts, + end_ts, null, false, Aggregators.SUM, 0, null)); + final ArrayList spans = Whitebox.getInternalState(group, "spans"); + spans.add(span); + + assertArrayEquals(new byte[] { 0, 0, 1 }, group.metricUID()); + } + @Test public void getTagUids() throws Exception { final ByteMap uids = new ByteMap(); @@ -59,9 +73,9 @@ public void getTagUids() throws Exception { final ByteMap uids_read = group.getTagUids(); assertEquals(1, uids_read.size()); - assertEquals(0, Bytes.memcmp(new byte[] { 0, 0, 1 }, uids_read.firstKey())); - assertEquals(0, Bytes.memcmp(new byte[] { 0, 0, 2 }, - uids_read.firstEntry().getValue())); + assertArrayEquals(new byte[] { 0, 0, 1 }, uids_read.firstKey()); + assertArrayEquals(new byte[] { 0, 0, 2 }, + uids_read.firstEntry().getValue()); } @Test @@ -94,4 +108,53 @@ public void getTagUidsNoSpans() throws Exception { final ByteMap uids_read = group.getTagUids(); assertEquals(0, uids_read.size()); } + + @Test + public void getAggregatedTagUidsNotAgged() throws Exception { + final ByteMap uids = new ByteMap(); + uids.put(new byte[] { 0, 0, 1 }, new byte[] { 0, 0, 2 }); + final Span span = mock(Span.class); + when(span.getTagUids()).thenReturn(uids); + + final SpanGroup group = PowerMockito.spy(new SpanGroup(tsdb, start_ts, + end_ts, null, false, Aggregators.SUM, 0, null)); + final ArrayList spans = Whitebox.getInternalState(group, "spans"); + spans.add(span); + + final List uids_read = group.getAggregatedTagUids(); + assertEquals(0, uids_read.size()); + } + + @Test + public void getAggregatedTagUids() throws Exception { + final ByteMap uids = new ByteMap(); + uids.put(new byte[] { 0, 0, 1 }, new byte[] { 0, 0, 2 }); + final Span span = mock(Span.class); + when(span.getTagUids()).thenReturn(uids); + + final ByteMap uids2 = new ByteMap(); + uids2.put(new byte[] { 0, 0, 1 }, new byte[] { 0, 0, 0, 3 }); + final Span span2 = mock(Span.class); + when(span2.getTagUids()).thenReturn(uids2); + + final SpanGroup group = PowerMockito.spy(new SpanGroup(tsdb, start_ts, + end_ts, null, false, Aggregators.SUM, 0, null)); + final ArrayList spans = Whitebox.getInternalState(group, "spans"); + spans.add(span); + spans.add(span2); + + final List uids_read = group.getAggregatedTagUids(); + assertEquals(1, uids_read.size()); + assertArrayEquals(new byte[] { 0, 0, 1 }, uids_read.get(0)); + } + + @Test + public void getAggregatedTagUidsNoSpans() throws Exception { + final SpanGroup group = new SpanGroup(tsdb, start_ts, end_ts, null, + false, Aggregators.SUM, 0, null); + + final List uids_read = group.getAggregatedTagUids(); + assertEquals(0, uids_read.size()); + } + } From cb9d50ad8d86b4ea8a6fb901c22454c4ce690bef Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Wed, 4 Nov 2015 14:52:00 -0800 Subject: [PATCH 059/233] Add the SCALAR fill policy and Jacksonify the FillPolicay class for serdes. Add the NumericFillPolicy class for further serdes (that can take scalar values) for use with expressions. Signed-off-by: Chris Larsen --- Makefile.am | 2 + src/core/FillPolicy.java | 11 +- src/query/expression/NumericFillPolicy.java | 176 ++++++++++ .../expression/TestNumericFillPolicy.java | 304 ++++++++++++++++++ test/tsd/TestQueryRpc.java | 3 +- 5 files changed, 493 insertions(+), 3 deletions(-) create mode 100644 src/query/expression/NumericFillPolicy.java create mode 100644 test/query/expression/TestNumericFillPolicy.java diff --git a/Makefile.am b/Makefile.am index 1b07e4c556..f207613c96 100644 --- a/Makefile.am +++ b/Makefile.am @@ -82,6 +82,7 @@ tsdb_SRC := \ src/query/expression/ExpressionTree.java \ src/query/expression/HighestCurrent.java \ src/query/expression/HighestMax.java \ + src/query/expression/NumericFillPolicy.java \ src/query/expression/MovingAverage.java \ src/query/expression/PostAggregatedDataPoints.java \ src/query/expression/Scale.java \ @@ -248,6 +249,7 @@ test_SRC := \ test/query/expression/TestExpressionTree.java \ test/query/expression/TestHighestCurrent.java \ test/query/expression/TestHighestMax.java \ + test/query/expression/TestNumericFillPolicy.java \ test/query/expression/TestMovingAverage.java \ test/query/expression/TestPostAggregatedDataPoints.java \ test/query/expression/TestScale.java \ diff --git a/src/core/FillPolicy.java b/src/core/FillPolicy.java index 7cba9bc5d9..09c145f284 100644 --- a/src/core/FillPolicy.java +++ b/src/core/FillPolicy.java @@ -12,6 +12,9 @@ // see . package net.opentsdb.core; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + /** * Specification of how to deal with missing intervals when downsampling. * @since 2.2 @@ -20,7 +23,8 @@ public enum FillPolicy { NONE("none"), ZERO("zero"), NOT_A_NUMBER("nan"), - NULL("null"); + NULL("null"), + SCALAR("scalar"); // The user-friendly name of this policy. private final String name; @@ -33,6 +37,7 @@ public enum FillPolicy { * Get this fill policy's user-friendly name. * @return this fill policy's user-friendly name. */ + @JsonValue public String getName() { return name; } @@ -42,7 +47,9 @@ public String getName() { * @param name The user-friendly name of a fill policy. * @return an instance of {@link FillPolicy}, or {@code null} if the name * does not match any instance. + * @throws IllegalArgumentException if the name doesn't match a policy */ + @JsonCreator public static FillPolicy fromString(final String name) { for (final FillPolicy policy : FillPolicy.values()) { if (policy.name.equalsIgnoreCase(name)) { @@ -50,7 +57,7 @@ public static FillPolicy fromString(final String name) { } } - return null; + throw new IllegalArgumentException("Unrecognized fill policy: " + name); } } diff --git a/src/query/expression/NumericFillPolicy.java b/src/query/expression/NumericFillPolicy.java new file mode 100644 index 0000000000..cf0f4673d7 --- /dev/null +++ b/src/query/expression/NumericFillPolicy.java @@ -0,0 +1,176 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.common.base.Objects; + +import net.opentsdb.core.FillPolicy; + +/** + * POJO for serdes of fill policies. It allows the user to pick either policies + * with default values or a scalar that can be supplied with any number. + * @since 2.3 + */ +@JsonDeserialize(builder = NumericFillPolicy.Builder.class) +public class NumericFillPolicy { + + /** The fill policy to use. This is required */ + private FillPolicy policy; + + /** The value to store with the fill policy */ + private double value; + + /** + * CTor to set the policy. Also calls {@link #validate()} + * @param policy The policy to set. + */ + public NumericFillPolicy(final FillPolicy policy) { + this.policy = policy; + validate(); + } + + /** + * CTor to set the policy and value. Also calls {@link #validate()} + * @param policy The name of the fill policy + * @param value The value to use when filling + * @throws IllegalArgumentException if the policy and value don't gel together + */ + public NumericFillPolicy(final FillPolicy policy, final double value) { + this.policy = policy; + this.value = value; + validate(); + } + + @Override + public String toString() { + return "policy=" + policy + ", value=" + value; + } + + /** @returns a NumericFillPolicy builder */ + public static Builder Builder() { + return new Builder(); + } + + /** + * A builder class for deserialization via Jackson + */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPOJOBuilder(buildMethodName = "build", withPrefix = "") + public static final class Builder { + @JsonProperty + private FillPolicy policy; + @JsonProperty + private double value; + + public Builder setPolicy(FillPolicy policy) { + this.policy = policy; + return this; + } + + public Builder setValue(double value) { + this.value = value; + return this; + } + + public NumericFillPolicy build() { + return new NumericFillPolicy(policy, value); + } + } + + @Override + public int hashCode() { + return Objects.hashCode(policy, value); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + if (!(obj instanceof NumericFillPolicy)) { + return false; + } + final NumericFillPolicy nfp = (NumericFillPolicy)obj; + return Objects.equal(policy, nfp.policy) && + Objects.equal(value, nfp.value); + } + + /** @return the fill policy */ + public FillPolicy getPolicy() { + return policy; + } + + /** @param policy the fill policy to use */ + public void setPolicy(final FillPolicy policy) { + this.policy = policy; + } + + /** @return the value to use when filling */ + public double getValue() { + return value; + } + + /** @param value the value to use when filling */ + public void setValue(final double value) { + this.value = value; + } + + /** + * Makes sure the policy name and value are a suitable combination. If one + * or the other is missing then we set the other with the proper value. + * @throws IllegalArgumentException if the combination is bad + */ + public void validate() { + if (policy == null) { + if (value == 0) { + policy = FillPolicy.ZERO; + } else if (Double.isNaN(value)) { + policy = FillPolicy.NOT_A_NUMBER; + } else { + policy = FillPolicy.SCALAR; + } + } else { + switch (policy) { + case NONE: + case NOT_A_NUMBER: + if (value != 0 && !Double.isNaN(value)) { + throw new IllegalArgumentException( + "The value for NONE and NAN must be NaN"); + } + value = Double.NaN; + break; + case ZERO: + if (value != 0) { + throw new IllegalArgumentException("The value for ZERO must be 0"); + } + value = 0; + break; + case NULL: + if (value != 0 && !Double.isNaN(value)) { + throw new IllegalArgumentException("The value for NULL must be 0"); + } + value = Double.NaN; + break; + case SCALAR: // it CAN be zero + break; + } + } + } +} diff --git a/test/query/expression/TestNumericFillPolicy.java b/test/query/expression/TestNumericFillPolicy.java new file mode 100644 index 0000000000..2b83d68b5c --- /dev/null +++ b/test/query/expression/TestNumericFillPolicy.java @@ -0,0 +1,304 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import net.opentsdb.core.FillPolicy; +import net.opentsdb.utils.JSON; + +import org.junit.Test; + +public class TestNumericFillPolicy { + + @Test + public void builder() throws Exception { + NumericFillPolicy nfp = NumericFillPolicy.Builder() + .setPolicy(FillPolicy.NOT_A_NUMBER).build(); + assertEquals(FillPolicy.NOT_A_NUMBER, nfp.getPolicy()); + assertTrue(Double.isNaN((Double)nfp.getValue())); + + nfp = NumericFillPolicy.Builder() + .setPolicy(null).build(); + assertEquals(FillPolicy.ZERO, nfp.getPolicy()); + assertEquals(0, nfp.getValue(), 0.0001); + } + + @Test + public void policyCtor() throws Exception { + NumericFillPolicy nfp = new NumericFillPolicy(FillPolicy.NONE); + assertTrue(Double.isNaN((Double)nfp.getValue())); + assertEquals(FillPolicy.NONE, nfp.getPolicy()); + + nfp = new NumericFillPolicy(FillPolicy.NOT_A_NUMBER); + assertTrue(Double.isNaN((Double)nfp.getValue())); + assertEquals(FillPolicy.NOT_A_NUMBER, nfp.getPolicy()); + + nfp = new NumericFillPolicy(FillPolicy.NULL); + assertTrue(Double.isNaN((Double)nfp.getValue())); + assertEquals(FillPolicy.NULL, nfp.getPolicy()); + + nfp = new NumericFillPolicy(FillPolicy.ZERO); + assertEquals(0, nfp.getValue(), 0.0001); + assertEquals(FillPolicy.ZERO, nfp.getPolicy()); + + nfp = new NumericFillPolicy(null); + assertEquals(0, nfp.getValue(), 0.0001); + assertEquals(FillPolicy.ZERO, nfp.getPolicy()); + + nfp = new NumericFillPolicy(FillPolicy.SCALAR); + assertEquals(0, nfp.getValue(), 0.0001); + assertEquals(FillPolicy.SCALAR, nfp.getPolicy()); + + } + + @Test + public void policyAndValueCtor() throws Exception { + NumericFillPolicy nfp = new NumericFillPolicy(FillPolicy.NONE, Double.NaN); + assertTrue(Double.isNaN((Double)nfp.getValue())); + assertEquals(FillPolicy.NONE, nfp.getPolicy()); + + nfp = new NumericFillPolicy(FillPolicy.NOT_A_NUMBER, Double.NaN); + assertTrue(Double.isNaN((Double)nfp.getValue())); + assertEquals(FillPolicy.NOT_A_NUMBER, nfp.getPolicy()); + + nfp = new NumericFillPolicy(FillPolicy.NULL, 0); + assertTrue(Double.isNaN((Double)nfp.getValue())); + assertEquals(FillPolicy.NULL, nfp.getPolicy()); + + nfp = new NumericFillPolicy(FillPolicy.ZERO, 0); + assertEquals(0, nfp.getValue(), 0.0001); + assertEquals(FillPolicy.ZERO, nfp.getPolicy()); + + nfp = new NumericFillPolicy(null, 0); + assertEquals(0, nfp.getValue(), 0.0001); + assertEquals(FillPolicy.ZERO, nfp.getPolicy()); + + nfp = new NumericFillPolicy(FillPolicy.SCALAR, 42); + assertEquals(42, nfp.getValue(), 0.0001); + assertEquals(FillPolicy.SCALAR, nfp.getPolicy()); + + nfp = new NumericFillPolicy(FillPolicy.SCALAR, 0); + assertEquals(0, nfp.getValue(), 0.0001); + assertEquals(FillPolicy.SCALAR, nfp.getPolicy()); + + nfp = new NumericFillPolicy(FillPolicy.SCALAR, Double.NaN); + assertTrue(Double.isNaN((Double)nfp.getValue())); + assertEquals(FillPolicy.SCALAR, nfp.getPolicy()); + + nfp = new NumericFillPolicy(FillPolicy.SCALAR, 42.5); + assertEquals(42.5, (Double)nfp.getValue(), 0.0001); + assertEquals(FillPolicy.SCALAR, nfp.getPolicy()); + + // defaults from value + nfp = new NumericFillPolicy(null, Double.NaN); + assertTrue(Double.isNaN((Double)nfp.getValue())); + assertEquals(FillPolicy.NOT_A_NUMBER, nfp.getPolicy()); + + nfp = new NumericFillPolicy(null, 42); + assertEquals(42, nfp.getValue(), 0.0001); + assertEquals(FillPolicy.SCALAR, nfp.getPolicy()); + + nfp = new NumericFillPolicy(null, 42.5); + assertEquals(42.5, (Double)nfp.getValue(), 0.0001); + assertEquals(FillPolicy.SCALAR, nfp.getPolicy()); + + nfp = new NumericFillPolicy(null, -42.5); + assertEquals(-42.5, (Double)nfp.getValue(), 0.0001); + assertEquals(FillPolicy.SCALAR, nfp.getPolicy()); + + nfp = new NumericFillPolicy(null, Double.NaN); + assertTrue(Double.isNaN((Double)nfp.getValue())); + assertEquals(FillPolicy.NOT_A_NUMBER, nfp.getPolicy()); + + nfp = new NumericFillPolicy(null, 0); + assertEquals(0, nfp.getValue(), 0.0001); + assertEquals(FillPolicy.ZERO, nfp.getPolicy()); + + nfp = new NumericFillPolicy(null, 0.0); + assertEquals(0.0, (Double)nfp.getValue(), 0.0001); + assertEquals(FillPolicy.ZERO, nfp.getPolicy()); + + nfp = new NumericFillPolicy(null, -0.0); + assertEquals(-0.0, (Double)nfp.getValue(), 0.0001); + assertEquals(FillPolicy.ZERO, nfp.getPolicy()); + + nfp = new NumericFillPolicy(null, -0); + assertEquals(-0, nfp.getValue(), 0.0001); + assertEquals(FillPolicy.ZERO, nfp.getPolicy()); + + nfp = new NumericFillPolicy(FillPolicy.NOT_A_NUMBER, 0); + assertTrue(Double.isNaN((Double)nfp.getValue())); + assertEquals(FillPolicy.NOT_A_NUMBER, nfp.getPolicy()); + + nfp = new NumericFillPolicy(FillPolicy.NULL, Double.NaN); + assertTrue(Double.isNaN((Double)nfp.getValue())); + assertEquals(FillPolicy.NULL, nfp.getPolicy()); + + // inappropriate combos + try { + nfp = new NumericFillPolicy(FillPolicy.ZERO, 42); + fail("expected an IllegalArgumentException"); + } catch (IllegalArgumentException iae) { } + + try { + nfp = new NumericFillPolicy(FillPolicy.NONE, 42); + fail("expected an IllegalArgumentException"); + } catch (IllegalArgumentException iae) { } + + try { + nfp = new NumericFillPolicy(FillPolicy.NULL, 42); + fail("expected an IllegalArgumentException"); + } catch (IllegalArgumentException iae) { } + + try { + nfp = new NumericFillPolicy(FillPolicy.NOT_A_NUMBER, 42); + fail("expected an IllegalArgumentException"); + } catch (IllegalArgumentException iae) { } + + } + + @Test + public void serdes() throws Exception { + + NumericFillPolicy ser_nfp = new NumericFillPolicy(FillPolicy.NONE); + String json = JSON.serializeToString(ser_nfp); + assertTrue(json.contains("\"policy\":\"none\"")); + assertTrue(json.contains("\"value\":\"NaN\"")); + NumericFillPolicy des_nfp = JSON.parseToObject(json, NumericFillPolicy.class); + assertTrue(des_nfp != ser_nfp); + assertTrue(des_nfp.equals(ser_nfp)); + + ser_nfp = new NumericFillPolicy(FillPolicy.ZERO); + json = JSON.serializeToString(ser_nfp); + assertTrue(json.contains("\"policy\":\"zero\"")); + assertTrue(json.contains("\"value\":0")); + des_nfp = JSON.parseToObject(json, NumericFillPolicy.class); + assertTrue(des_nfp != ser_nfp); + assertTrue(des_nfp.equals(ser_nfp)); + + ser_nfp = new NumericFillPolicy(FillPolicy.NOT_A_NUMBER); + json = JSON.serializeToString(ser_nfp); + assertTrue(json.contains("\"policy\":\"nan\"")); + assertTrue(json.contains("\"value\":\"NaN\"")); + des_nfp = JSON.parseToObject(json, NumericFillPolicy.class); + assertTrue(des_nfp != ser_nfp); + assertTrue(des_nfp.equals(ser_nfp)); + + ser_nfp = new NumericFillPolicy(FillPolicy.NULL); + json = JSON.serializeToString(ser_nfp); + assertTrue(json.contains("\"policy\":\"null\"")); + assertTrue(json.contains("\"value\":\"NaN\"")); + des_nfp = JSON.parseToObject(json, NumericFillPolicy.class); + assertTrue(des_nfp != ser_nfp); + assertTrue(des_nfp.equals(ser_nfp)); + + ser_nfp = new NumericFillPolicy(FillPolicy.SCALAR, 42); + json = JSON.serializeToString(ser_nfp); + assertTrue(json.contains("\"policy\":\"scalar\"")); + assertTrue(json.contains("\"value\":42")); + des_nfp = JSON.parseToObject(json, NumericFillPolicy.class); + assertTrue(des_nfp != ser_nfp); + assertTrue(des_nfp.equals(ser_nfp)); + + ser_nfp = new NumericFillPolicy(FillPolicy.SCALAR, 42.5); + json = JSON.serializeToString(ser_nfp); + assertTrue(json.contains("\"policy\":\"scalar\"")); + assertTrue(json.contains("\"value\":42.5")); + des_nfp = JSON.parseToObject(json, NumericFillPolicy.class); + assertTrue(des_nfp != ser_nfp); + assertTrue(des_nfp.equals(ser_nfp)); + + ser_nfp = new NumericFillPolicy(FillPolicy.SCALAR, -42.5); + json = JSON.serializeToString(ser_nfp); + assertTrue(json.contains("\"policy\":\"scalar\"")); + assertTrue(json.contains("\"value\":-42.5")); + des_nfp = JSON.parseToObject(json, NumericFillPolicy.class); + assertTrue(des_nfp != ser_nfp); + assertTrue(des_nfp.equals(ser_nfp)); + + json = "{\"policy\":\"zero\"}"; + des_nfp = JSON.parseToObject(json, NumericFillPolicy.class); + assertEquals(FillPolicy.ZERO, des_nfp.getPolicy()); + assertEquals(0, des_nfp.getValue(), 0.0001); + + json = "{\"policy\":\"nan\"}"; + des_nfp = JSON.parseToObject(json, NumericFillPolicy.class); + assertEquals(FillPolicy.NOT_A_NUMBER, des_nfp.getPolicy()); + assertTrue(Double.isNaN((Double)des_nfp.getValue())); + + json = "{\"policy\":\"scalar\"}"; + des_nfp = JSON.parseToObject(json, NumericFillPolicy.class); + assertEquals(FillPolicy.SCALAR, des_nfp.getPolicy()); + assertEquals(0, des_nfp.getValue(), 0.0001); + + json = "{\"policy\":\"none\"}"; + des_nfp = JSON.parseToObject(json, NumericFillPolicy.class); + assertEquals(FillPolicy.NONE, des_nfp.getPolicy()); + assertTrue(Double.isNaN((Double)des_nfp.getValue())); + + json = "{\"policy\":\"null\"}"; + des_nfp = JSON.parseToObject(json, NumericFillPolicy.class); + assertEquals(FillPolicy.NULL, des_nfp.getPolicy()); + assertTrue(Double.isNaN((Double)des_nfp.getValue())); + + json = "{\"policy\":\"scalar\",\"value\":42}"; + des_nfp = JSON.parseToObject(json, NumericFillPolicy.class); + assertEquals(FillPolicy.SCALAR, des_nfp.getPolicy()); + assertEquals(42, des_nfp.getValue(), 0.0001); + + json = "{\"policy\":\"scalar\",\"value\":\"42\"}"; + des_nfp = JSON.parseToObject(json, NumericFillPolicy.class); + assertEquals(FillPolicy.SCALAR, des_nfp.getPolicy()); + assertEquals(42, des_nfp.getValue(), 0.0001); + + json = "{\"policy\":\"scalar\",\"value\":42.5}"; + des_nfp = JSON.parseToObject(json, NumericFillPolicy.class); + assertEquals(FillPolicy.SCALAR, des_nfp.getPolicy()); + assertEquals(42.5, (Double)des_nfp.getValue(), 0.0001); + + json = "{\"policy\":\"nan\",\"value\":NaN}"; + des_nfp = JSON.parseToObject(json, NumericFillPolicy.class); + assertEquals(FillPolicy.NOT_A_NUMBER, des_nfp.getPolicy()); + assertTrue(Double.isNaN((Double)des_nfp.getValue())); + + json = "{\"policy\":\"scalar\",\"value\":0}"; + des_nfp = JSON.parseToObject(json, NumericFillPolicy.class); + assertEquals(FillPolicy.SCALAR, des_nfp.getPolicy()); + assertEquals(0, des_nfp.getValue(), 0.0001); + + json = "{\"policy\":\"scalar\",\"value\":0.0}"; + des_nfp = JSON.parseToObject(json, NumericFillPolicy.class); + assertEquals(FillPolicy.SCALAR, des_nfp.getPolicy()); + assertEquals(0.0, (Double)des_nfp.getValue(), 0.0001); + + try { + json = "{\"policy\":\"unknown\"}"; + des_nfp = JSON.parseToObject(json, NumericFillPolicy.class); + fail("Expected a IllegalArgumentException"); + } catch (IllegalArgumentException e) { } + + try { + json = "{\"policy\":\"scalar\",value\":\"foo\"}"; + des_nfp = JSON.parseToObject(json, NumericFillPolicy.class); + fail("Expected a IllegalArgumentException"); + } catch (IllegalArgumentException e) { } + + try { + json = "{\"policy\":\"badjson"; + des_nfp = JSON.parseToObject(json, NumericFillPolicy.class); + fail("Expected a IllegalArgumentException"); + } catch (IllegalArgumentException e) { } + } +} diff --git a/test/tsd/TestQueryRpc.java b/test/tsd/TestQueryRpc.java index cce155c244..b4f0f5caa1 100644 --- a/test/tsd/TestQueryRpc.java +++ b/test/tsd/TestQueryRpc.java @@ -520,8 +520,9 @@ public void executeWithBadDSFill() throws Exception { rpc.execute(tsdb, query); fail("expected BadRequestException"); } catch (final BadRequestException exn) { + System.out.println(exn.getMessage()); assertTrue(exn.getMessage().startsWith( - "No such fill policy: 'badbadbad': must be one of:")); + "Unrecognized fill policy: badbadbad")); } } From 131f00e4cda6e8d6bb93d4d4a403c7c9c2373618 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Wed, 4 Nov 2015 15:11:02 -0800 Subject: [PATCH 060/233] Add the TSSubQuery.getFilterTagks() to return the list of tag keys the query deals with. Used for expressions. Signed-off-by: Chris Larsen --- src/core/TSSubQuery.java | 16 ++++++++++++++++ test/core/TestTSSubQuery.java | 15 +++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/core/TSSubQuery.java b/src/core/TSSubQuery.java index 7438f7d303..faae314fca 100644 --- a/src/core/TSSubQuery.java +++ b/src/core/TSSubQuery.java @@ -20,6 +20,7 @@ import java.util.NoSuchElementException; import net.opentsdb.query.filter.TagVFilter; +import net.opentsdb.utils.ByteSet; import com.google.common.base.Objects; import com.google.common.collect.ImmutableMap; @@ -273,6 +274,21 @@ public List getFilters() { return filters; } + /** @return the unique set of tagks from the filters. May be null if no filters + * were set. Must make sure to resolve the string tag to UIDs in the filter first. + * @since 2.3 + */ + public ByteSet getFilterTagKs() { + if (filters == null || filters.isEmpty()) { + return null; + } + final ByteSet tagks = new ByteSet(); + for (final TagVFilter filter : filters) { + tagks.add(filter.getTagkBytes()); + } + return tagks; + } + /** @param aggregator the name of an aggregation function */ public void setAggregator(String aggregator) { this.aggregator = aggregator; diff --git a/test/core/TestTSSubQuery.java b/test/core/TestTSSubQuery.java index 3336f18827..1b6ac0216e 100644 --- a/test/core/TestTSSubQuery.java +++ b/test/core/TestTSSubQuery.java @@ -12,6 +12,7 @@ // see . package net.opentsdb.core; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -29,6 +30,7 @@ import net.opentsdb.query.filter.TagVWildcardFilter; import org.junit.Test; +import org.powermock.reflect.Whitebox; public final class TestTSSubQuery { @@ -185,6 +187,19 @@ public void validateWithGroupByFilter() { assertEquals(300000, sub.downsampleInterval()); } + @Test + public void getFilterTagks() { + final TagVFilter filter = TagVFilter.Builder() + .setFilter("*nari").setType("wildcard").setTagk("host").build(); + Whitebox.setInternalState(filter, "tagk_bytes", new byte[] { 0, 0, 1 }); + TSSubQuery sub = getMetricForValidate(); + sub.setFilters(Arrays.asList(filter)); + sub.validateAndSetQuery(); + + assertEquals(1, sub.getFilterTagKs().size()); + assertArrayEquals(new byte[] { 0, 0, 1 }, sub.getFilterTagKs().iterator().next()); + } + // NOTE: Each of the hash and equals tests should make sure that we the code // doesn't change after validation. From 5c6d6a41a090a3429858205fc2da31d7f2ed3e7c Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Wed, 4 Nov 2015 18:06:39 -0800 Subject: [PATCH 061/233] Add more UIDs to the base TSDB test class, UIDs for A to Z so that we have more to play around with. Signed-off-by: Chris Larsen --- test/core/BaseTsdbTest.java | 91 +++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/test/core/BaseTsdbTest.java b/test/core/BaseTsdbTest.java index d51efd5601..2f83b61243 100644 --- a/test/core/BaseTsdbTest.java +++ b/test/core/BaseTsdbTest.java @@ -59,6 +59,25 @@ @PrepareForTest({TSDB.class, Config.class, UniqueId.class, HBaseClient.class, HashedWheelTimer.class, Scanner.class, Const.class }) public class BaseTsdbTest { + /** A list of UIDs from A to Z for unit testing UIDs values */ + public static final Map METRIC_UIDS = + new HashMap(26); + public static final Map TAGK_UIDS = + new HashMap(26); + public static final Map TAGV_UIDS = + new HashMap(26); + static { + char letter = 'A'; + int uid = 10; + for (int i = 0; i < 26; i++) { + METRIC_UIDS.put(Character.toString(letter), + UniqueId.longToUID(uid, TSDB.metrics_width())); + TAGK_UIDS.put(Character.toString(letter), + UniqueId.longToUID(uid, TSDB.tagk_width())); + TAGV_UIDS.put(Character.toString(letter++), + UniqueId.longToUID(uid++, TSDB.tagv_width())); + } + } public static final String METRIC_STRING = "sys.cpu.user"; public static final byte[] METRIC_BYTES = new byte[] { 0, 0, 1 }; @@ -124,6 +143,7 @@ public void before() throws Exception { tags.put(TAGK_STRING, TAGV_STRING); } + /** Adds the static UIDs to the metrics UID mock object */ void setupMetricMaps() { when(metrics.getId(METRIC_STRING)).thenReturn(METRIC_BYTES); when(metrics.getIdAsync(METRIC_STRING)) @@ -174,8 +194,32 @@ public Deferred answer(InvocationOnMock invocation) when(metrics.getIdAsync(NSUN_METRIC)) .thenReturn(Deferred.fromError(nsun)); when(metrics.getOrCreateId(NSUN_METRIC)).thenThrow(nsun); + + // Iterate over the metric UIDs and handle both forward and reverse + for (final Map.Entry uid : METRIC_UIDS.entrySet()) { + when(metrics.getId(uid.getKey())).thenReturn(uid.getValue()); + when(metrics.getIdAsync(uid.getKey())) + .thenAnswer(new Answer>() { + @Override + public Deferred answer(InvocationOnMock invocation) + throws Throwable { + return Deferred.fromResult(uid.getValue()); + } + }); + when(metrics.getOrCreateId(uid.getKey())) + .thenReturn(uid.getValue()); + when(metrics.getNameAsync(uid.getValue())) + .thenAnswer(new Answer>() { + @Override + public Deferred answer(InvocationOnMock invocation) + throws Throwable { + return Deferred.fromResult(uid.getKey()); + } + }); + } } + /** Adds the static UIDs to the tag keys UID mock object */ void setupTagkMaps() { when(tag_names.getId(TAGK_STRING)).thenReturn(TAGK_BYTES); when(tag_names.getOrCreateId(TAGK_STRING)).thenReturn(TAGK_BYTES); @@ -228,8 +272,32 @@ public Deferred answer(InvocationOnMock invocation) .thenThrow(nsun); when(tag_names.getIdAsync(NSUN_TAGK)) .thenReturn(Deferred.fromError(nsun)); + + // Iterate over the tagk UIDs and handle both forward and reverse + for (final Map.Entry uid : TAGK_UIDS.entrySet()) { + when(tag_names.getId(uid.getKey())).thenReturn(uid.getValue()); + when(tag_names.getIdAsync(uid.getKey())) + .thenAnswer(new Answer>() { + @Override + public Deferred answer(InvocationOnMock invocation) + throws Throwable { + return Deferred.fromResult(uid.getValue()); + } + }); + when(tag_names.getOrCreateId(uid.getKey())) + .thenReturn(uid.getValue()); + when(tag_names.getNameAsync(uid.getValue())) + .thenAnswer(new Answer>() { + @Override + public Deferred answer(InvocationOnMock invocation) + throws Throwable { + return Deferred.fromResult(uid.getKey()); + } + }); + } } + /** Adds the static UIDs to the tag values UID mock object */ void setupTagvMaps() { when(tag_values.getId(TAGV_STRING)).thenReturn(TAGV_BYTES); when(tag_values.getOrCreateId(TAGV_STRING)).thenReturn(TAGV_BYTES); @@ -269,6 +337,29 @@ public Deferred answer(InvocationOnMock invocation) when(tag_values.getId(NSUN_TAGV)).thenThrow(nsun); when(tag_values.getIdAsync(NSUN_TAGV)) .thenReturn(Deferred.fromError(nsun)); + + // Iterate over the tagv UIDs and handle both forward and reverse + for (final Map.Entry uid : TAGV_UIDS.entrySet()) { + when(tag_values.getId(uid.getKey())).thenReturn(uid.getValue()); + when(tag_values.getIdAsync(uid.getKey())) + .thenAnswer(new Answer>() { + @Override + public Deferred answer(InvocationOnMock invocation) + throws Throwable { + return Deferred.fromResult(uid.getValue()); + } + }); + when(tag_values.getOrCreateId(uid.getKey())) + .thenReturn(uid.getValue()); + when(tag_values.getNameAsync(uid.getValue())) + .thenAnswer(new Answer>() { + @Override + public Deferred answer(InvocationOnMock invocation) + throws Throwable { + return Deferred.fromResult(uid.getKey()); + } + }); + } } // ----------------- // From 13a6ad0f02b84443b89b0d72980663adebdf2235 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Wed, 4 Nov 2015 14:57:26 -0800 Subject: [PATCH 062/233] Add the ITimeSyncedIterator.java interface for time synchronized iterator implementations. Add the ExpressionDataPoint class to store iterators, values and aggregations of expressions. Signed-off-by: Chris Larsen --- Makefile.am | 1 + src/query/expression/ExpressionDataPoint.java | 227 ++++++++++++++++++ src/query/expression/ITimeSyncedIterator.java | 75 ++++++ 3 files changed, 303 insertions(+) create mode 100644 src/query/expression/ExpressionDataPoint.java create mode 100644 src/query/expression/ITimeSyncedIterator.java diff --git a/Makefile.am b/Makefile.am index f207613c96..16698f5269 100644 --- a/Makefile.am +++ b/Makefile.am @@ -82,6 +82,7 @@ tsdb_SRC := \ src/query/expression/ExpressionTree.java \ src/query/expression/HighestCurrent.java \ src/query/expression/HighestMax.java \ + src/query/expression/ITimeSyncedIterator.java \ src/query/expression/NumericFillPolicy.java \ src/query/expression/MovingAverage.java \ src/query/expression/PostAggregatedDataPoints.java \ diff --git a/src/query/expression/ExpressionDataPoint.java b/src/query/expression/ExpressionDataPoint.java new file mode 100644 index 0000000000..216546869f --- /dev/null +++ b/src/query/expression/ExpressionDataPoint.java @@ -0,0 +1,227 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import java.util.HashSet; +import java.util.Set; + +import org.hbase.async.Bytes.ByteMap; + +import net.opentsdb.core.DataPoint; +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.MutableDataPoint; +import net.opentsdb.utils.ByteSet; + +/** + * Contains the information for a series that has been processed through an + * expression iterator. Each time a metric data point series set is added we + * add the metric and compute the tag sets. + *

+ * As the iterator progresses, it will call into the {@link #reset} methods. + * @since 2.3 + */ +public class ExpressionDataPoint implements DataPoint { + + /** A list of metric UIDs wrapped up into this expression result */ + private final ByteSet metric_uids; + + /** The list of tag key/value pairs common to all series in this expression */ + private final ByteMap tags; + + /** The list of aggregated tag keys common to all series in this expression */ + private final ByteSet aggregated_tags; + + /** The list of TSUIDs from all series in this expression */ + private final Set tsuids; + + /** The size of the aggregated results. + * TODO - this is simply the size of the first series added. We need a way + * to compute this properly. + */ + private long size; + + /** The total number of raw data points in all series */ + private long raw_size; + + /** The data point overwritten each time through the iterator */ + private final MutableDataPoint dp; + + /** + * Default ctor that simply sets up new objects for all internal fields. + * TODO - lazily initialize the field to avoid unused objects + */ + public ExpressionDataPoint() { + metric_uids = new ByteSet(); + tags = new ByteMap(); + aggregated_tags = new ByteSet(); + tsuids = new HashSet(); + dp = new MutableDataPoint(); + } + + /** + * Ctor that sets up the meta data maps and initializes an empty dp + * @param dps The data point to pull meta from + */ + @SuppressWarnings("unchecked") + public ExpressionDataPoint(final DataPoints dps) { + metric_uids = new ByteSet(); + metric_uids.add(dps.metricUID()); + tags = (ByteMap) dps.getTagUids().clone(); + aggregated_tags = new ByteSet(); + for (final byte[] tagk : dps.getAggregatedTagUids()) { + aggregated_tags.add(tagk); + } + tsuids = new HashSet(dps.getTSUIDs()); + // TODO - restore when these are faster + //size = dps.size(); + //raw_size = dps.aggregatedSize(); + dp = new MutableDataPoint(); + dp.reset(Long.MAX_VALUE, Double.NaN); + } + + /** + * Ctor that clones the meta data of the existing dps and sets up an empty value + * @param dps The data point to pull meta from + */ + @SuppressWarnings("unchecked") + public ExpressionDataPoint(final ExpressionDataPoint dps) { + metric_uids = new ByteSet(); + metric_uids.addAll(dps.metric_uids); + tags = (ByteMap) dps.tags.clone(); + aggregated_tags = new ByteSet(); + aggregated_tags.addAll(dps.aggregated_tags); + tsuids = new HashSet(dps.tsuids); + size = dps.size; + raw_size = dps.raw_size; + dp = new MutableDataPoint(); + dp.reset(Long.MAX_VALUE, Double.NaN); + } + + /** + * Add another metric series to this collection, computing the tag and + * agg intersections and incrementing the size. + * @param dps The series to add + */ + public void add(final DataPoints dps) { + metric_uids.add(dps.metricUID()); + + // TODO - tags intersection + + for (final byte[] tagk : dps.getAggregatedTagUids()) { + aggregated_tags.add(tagk); + } + + tsuids.addAll(dps.getTSUIDs()); + // TODO - this ain't right. We need to number of dps emitted from HERE. For + // now we'll just take the first dps size. If it's downsampled then this + // will be accurate. + //size += dps.size(); + // TODO - restore when this is faster + //raw_size += dps.aggregatedSize(); + } + + /** + * Add another metric series to this collection, computing the tag and + * agg intersections and incrementing the size. + * @param dps The series to add + */ + public void add(final ExpressionDataPoint dps) { + metric_uids.addAll(dps.metric_uids); + + // TODO - tags intersection + + aggregated_tags.addAll(dps.aggregated_tags); + + tsuids.addAll(dps.tsuids); + // TODO - this ain't right. We need to number of dps emitted from HERE. For + // now we'll just take the first dps size. If it's downsampled then this + // will be accurate. + //size += dps.size(); + raw_size += dps.raw_size; + } + + /** @return the metric UIDs */ + public ByteSet metricUIDs() { + return metric_uids; + } + + /** @return the list of common tag pairs in the series */ + public ByteMap tags() { + return tags; + } + + /** @return the list of aggregated tags */ + public ByteSet aggregatedTags() { + return aggregated_tags; + } + + /** @return the list of TSUIDs aggregated into this series */ + public Set tsuids() { + return tsuids; + } + + /** @return the aggregated number of data points in this series */ + public long size() { + return size; + } + + /** @return the number of raw data points in this series */ + public long rawSize() { + return raw_size; + } + + /** + * Stores a Double data point + * @param timestamp The timestamp + * @param value The value + */ + public void reset(final long timestamp, final double value) { + dp.reset(timestamp, value); + } + + /** + * Stores a data point pulled from the given data point interface + * @param dp the data point to read from + */ + public void reset(final DataPoint dp) { + this.dp.reset(dp); + } + + // DataPoint implementations + + @Override + public long timestamp() { + return dp.timestamp(); + } + + @Override + public boolean isInteger() { + return dp.isInteger(); + } + + @Override + public long longValue() { + return dp.longValue(); + } + + @Override + public double doubleValue() { + return dp.doubleValue(); + } + + @Override + public double toDouble() { + return dp.toDouble(); + } + +} diff --git a/src/query/expression/ITimeSyncedIterator.java b/src/query/expression/ITimeSyncedIterator.java new file mode 100644 index 0000000000..22efbc55ae --- /dev/null +++ b/src/query/expression/ITimeSyncedIterator.java @@ -0,0 +1,75 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import net.opentsdb.utils.ByteSet; + +/** + * An interface for expressions or queries that operate across time series + * and require point-by-point timestamp synchronization. + * @since 2.3 + */ +public interface ITimeSyncedIterator { + + /** @return true if any of the series in the set has another value */ + public boolean hasNext(); + + /** + * @param timestamp The timestamp to fastforward to + * @return The data point array for the given timestamp. Implementations + * may throw an exception if the timestamp is invliad or they may return an + * empty array. + */ + public ExpressionDataPoint[] next(final long timestamp); + + /** + * @return the next timestamp available in this set. + */ + public long nextTimestamp(); + + /** @return the number of series in this set */ + public int size(); + + /** @return an array of the emitters populated during iteration */ + public ExpressionDataPoint[] values(); + + /** @param index the index to null. Nulls the given object so we don't use it + * in timestamps. + */ + public void nullIterator(final int index); + + /** @return the index in the ExpressionIterator */ + public int getIndex(); + + /** @param the index in the ExpressionIterator */ + public void setIndex(final int index); + + /** @return the ID of this set given by the user */ + public String getId(); + + /** @return a set of unique tag key UIDs from the filter list. If no filters + * were defined then the set may be empty. */ + public ByteSet getQueryTagKs(); + + /** @param A fill policy for the iterator. Iterators should implement a default */ + public void setFillPolicy(final NumericFillPolicy policy); + + /** @return the fill policy for the iterator */ + public NumericFillPolicy getFillPolicy(); + + /** @return a copy of the iterator. This should return references to + * underlying data objects but not necessarily copy all of the underlying + * data (to avoid memory explosions). This is useful for creating other + * iterators that operate over the same data. */ + public ITimeSyncedIterator getCopy(); +} From 1d94677443ea2dc42ea6dee91415e343ce6514ec Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Wed, 4 Nov 2015 17:46:46 -0800 Subject: [PATCH 063/233] Add the TimeSyncedIterator for working with expressions. Also add a base expression test class that stores data in MockBase and sets up a number of queries for testing Signed-off-by: Chris Larsen --- Makefile.am | 4 + src/query/expression/TimeSyncedIterator.java | 238 +++++++ .../BaseTimeSyncedIteratorTest.java | 649 ++++++++++++++++++ .../expression/TestTimeSyncedIterator.java | 504 ++++++++++++++ 4 files changed, 1395 insertions(+) create mode 100644 src/query/expression/TimeSyncedIterator.java create mode 100644 test/query/expression/BaseTimeSyncedIteratorTest.java create mode 100644 test/query/expression/TestTimeSyncedIterator.java diff --git a/Makefile.am b/Makefile.am index 16698f5269..0ce731dab0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -76,6 +76,7 @@ tsdb_SRC := \ src/query/QueryUtil.java \ src/query/expression/Absolute.java \ src/query/expression/Expression.java \ + src/query/expression/ExpressionDataPoint.java \ src/query/expression/ExpressionFactory.java \ src/query/expression/ExpressionReader.java \ src/query/expression/Expressions.java \ @@ -87,6 +88,7 @@ tsdb_SRC := \ src/query/expression/MovingAverage.java \ src/query/expression/PostAggregatedDataPoints.java \ src/query/expression/Scale.java \ + src/query/expression/TimeSyncedIterator.java \ src/query/filter/TagVFilter.java \ src/query/filter/TagVLiteralOrFilter.java \ src/query/filter/TagVNotKeyFilter.java \ @@ -243,6 +245,7 @@ test_SRC := \ test/meta/TestTSMeta.java \ test/meta/TestTSUIDQuery.java \ test/meta/TestUIDMeta.java \ + test/query/expression/BaseTimeSyncedIteratorTest.java \ test/query/expression/TestAbsolute.java \ test/query/expression/TestExpressionFactory.java \ test/query/expression/TestExpressionReader.java \ @@ -254,6 +257,7 @@ test_SRC := \ test/query/expression/TestMovingAverage.java \ test/query/expression/TestPostAggregatedDataPoints.java \ test/query/expression/TestScale.java \ + test/query/expression/TestTimeSyncedIterator.java \ test/query/filter/TestTagVFilter.java \ test/query/filter/TestTagVLiteralOrFilter.java \ test/query/filter/TestTagVNotKeyFilter.java \ diff --git a/src/query/expression/TimeSyncedIterator.java b/src/query/expression/TimeSyncedIterator.java new file mode 100644 index 0000000000..58d562b0cd --- /dev/null +++ b/src/query/expression/TimeSyncedIterator.java @@ -0,0 +1,238 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import net.opentsdb.core.DataPoint; +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.FillPolicy; +import net.opentsdb.core.SeekableView; +import net.opentsdb.utils.ByteSet; + +/** + * Holds the results of a sub query (a single metric) and iterates over each + * resultant series in lock-step for expression evaluation. + * @since 2.3 + */ +public class TimeSyncedIterator implements ITimeSyncedIterator { + + /** The name of this sub query given by the user */ + private final String id; + + /** The set of tag keys issued with the query */ + private final ByteSet query_tagks; + + /** The data point interfaces fetched from storage */ + private final DataPoints[] dps; + + /** The current value used for iterating */ + private final DataPoint[] current_values; + + /** References to the MutableDataObjects the ExpressionIterator will read */ + private final ExpressionDataPoint[] emitter_values; + + /** A list of the iterators used for fetching the next value */ + private final SeekableView[] iterators; + + /** Set by the ExpressionIterator when it computes the intersection */ + private int index; + + /** A policy to use for emitting values when a timestamp is missing data */ + private NumericFillPolicy fill_policy; + + /** + * Instantiates an iterator based on the results of a TSSubQuery. + * This will setup the emitters so it's safe to call {@link #values()} + * @param id The name of the query. + * @param query_tagks The set of tags used in filters on the query. + * @param dps The data points fetched from storage. + * @throws IllegalArgumentException if one of the parameters is null or the ID + * is empty + */ + public TimeSyncedIterator(final String id, final ByteSet query_tagks, + final DataPoints[] dps) { + if (id == null || id.isEmpty()) { + throw new IllegalArgumentException("Missing ID string"); + } + if (dps == null) { + // it's ok for these to be empty, but they canna be null ya ken? + throw new IllegalArgumentException("Missing data points"); + } + this.id = id; + this.query_tagks = query_tagks; + this.dps = dps; + // TODO - load from a default or something + fill_policy = new NumericFillPolicy(FillPolicy.ZERO); + current_values = new DataPoint[dps.length]; + emitter_values = new ExpressionDataPoint[dps.length]; + iterators = new SeekableView[dps.length]; + setupEmitters(); + } + + /** + * A copy constructor that loads from an existing iterator. + * @param iterator The iterator to load from + */ + private TimeSyncedIterator(final TimeSyncedIterator iterator) { + id = iterator.id; + query_tagks = iterator.query_tagks; // sharing is ok here + dps = iterator.dps; // TODO ?? OK? + fill_policy = iterator.fill_policy; + current_values = new DataPoint[dps.length]; + emitter_values = new ExpressionDataPoint[dps.length]; + iterators = new SeekableView[dps.length]; + setupEmitters(); + } + + @Override + public String toString() { + final StringBuilder buf = new StringBuilder(); + buf.append("TimeSyncedIterator(id=") + .append(id) + .append(", index=") + .append(index) + .append(", dpsSize=") + .append(dps.length) + .append(")"); + return buf.toString(); + } + + @Override + public int size() { + return dps.length; + } + + @Override + public boolean hasNext() { + for (final DataPoint dp : current_values) { + if (dp != null) { + return true; + } + } + return false; + } + + @Override + public ExpressionDataPoint[] next(final long timestamp) { + for (int i = 0; i < current_values.length; i++) { + if (current_values[i] == null) { + emitter_values[i].reset(timestamp, fill_policy.getValue()); + continue; + } + + if (current_values[i].timestamp() > timestamp) { + emitter_values[i].reset(timestamp, fill_policy.getValue()); + } else { + emitter_values[i].reset(current_values[i]); + next(i); // move to the next value for this guy + } + } + return emitter_values; + } + + @Override + public long nextTimestamp() { + long ts = Long.MAX_VALUE; + for (final DataPoint dp : current_values) { + if (dp != null) { + long t = dp.timestamp(); + if (t < ts) { + ts = t; + } + } + } + return ts; + } + + /** + * Moves the selected series to the next value. If the iterator has been + * nulled out (no more data) then this is a no-op. + * @param i The iterator index to advance + */ + private void next(final int i) { + if (!iterators[i].hasNext()) { + current_values[i] = null; + return; + } + current_values[i] = iterators[i].next(); + } + + @Override + public int getIndex() { + return index; + } + + @Override + public void setIndex(final int index) { + this.index = index; + } + + @Override + public String getId() { + return id; + } + + /** @return the set of data points */ + public DataPoints[] getDataPoints() { + return dps; + } + + @Override + public void nullIterator(final int index) { + if (index < 0 || index > current_values.length) { + throw new IllegalArgumentException("Index out of range: " + index); + } + current_values[index] = null; + } + + @Override + public ExpressionDataPoint[] values() { + return emitter_values; + } + + @Override + public ByteSet getQueryTagKs() { + return query_tagks; + } + + @Override + public void setFillPolicy(final NumericFillPolicy policy) { + fill_policy = policy; + } + + @Override + public NumericFillPolicy getFillPolicy() { + return fill_policy; + } + + @Override + public ITimeSyncedIterator getCopy() { + return new TimeSyncedIterator(this); + } + + /** + * Iterates over the values and sets up the current and emitter values + */ + private void setupEmitters() { + // set the iterators + for (int i = 0; i < dps.length; i++) { + iterators[i] = dps[i].iterator(); + if (!iterators[i].hasNext()) { + current_values[i] = null; + emitter_values[i] = null; + } else { + current_values[i] = iterators[i].next(); + emitter_values[i] = new ExpressionDataPoint(dps[i]); + } + } + } +} diff --git a/test/query/expression/BaseTimeSyncedIteratorTest.java b/test/query/expression/BaseTimeSyncedIteratorTest.java new file mode 100644 index 0000000000..4d8b0a0cb8 --- /dev/null +++ b/test/query/expression/BaseTimeSyncedIteratorTest.java @@ -0,0 +1,649 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import net.opentsdb.core.BaseTsdbTest; +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.Query; +import net.opentsdb.core.TSQuery; +import net.opentsdb.core.TSSubQuery; +import net.opentsdb.utils.Pair; + +/** + * A class for setting up a number of time series and queries for expression + * testing. + */ +public class BaseTimeSyncedIteratorTest extends BaseTsdbTest { + + /** Start time across all queries */ + protected static final long START_TS = 1388534400; + + /** The query object compiled after calling {@link #runQueries(ArrayList)} */ + protected TSQuery query; + + /** The results of our queries after calling {@link #runQueries(ArrayList)} */ + protected Map> results; + + /** List of iterators */ + protected Map iterators; + + /** + * Queries for metrics A and B with a group by all on the D tag + */ + protected void queryAB_Dstar() throws Exception { + final ArrayList subs = new ArrayList(2); + TSSubQuery sub = new TSSubQuery(); + + HashMap query_tags = new HashMap(1); + query_tags.put("D", "*"); + + sub = new TSSubQuery(); + sub.setMetric("A"); + sub.setTags(query_tags); + sub.setAggregator("sum"); + subs.add(sub); + + sub = new TSSubQuery(); + sub.setMetric("B"); + query_tags = new HashMap(1); + query_tags.put("D", "*"); + sub.setTags(query_tags); + sub.setAggregator("sum"); + subs.add(sub); + + runQueries(subs); + } + + /** + * Queries for A and B but without a tag specifier, thus agging em all + */ + protected void queryAB_AggAll() throws Exception { + final ArrayList subs = new ArrayList(2); + TSSubQuery sub = new TSSubQuery(); + + HashMap query_tags = new HashMap(1); + + sub = new TSSubQuery(); + sub.setMetric("A"); + sub.setTags(query_tags); + sub.setAggregator("sum"); + subs.add(sub); + + sub = new TSSubQuery(); + sub.setMetric("B"); + query_tags = new HashMap(1); + sub.setTags(query_tags); + sub.setAggregator("sum"); + subs.add(sub); + + runQueries(subs); + } + + /** + * Queries for A only with a filter of tag value "D" for tag key "D" + */ + protected void queryA_DD() throws Exception { + final ArrayList subs = new ArrayList(2); + TSSubQuery sub = new TSSubQuery(); + final HashMap query_tags = new HashMap(1); + query_tags.put("D", "D"); + sub = new TSSubQuery(); + sub.setMetric("A"); + sub.setTags(query_tags); + sub.setAggregator("sum"); + subs.add(sub); + + runQueries(subs); + } + + /** + * Executes the queries against MockBase through the regular pipeline and stores + * the results in {@linke #results} + * @param subs The queries to execute + */ + protected void runQueries(final ArrayList subs) throws Exception { + query = new TSQuery(); + query.setStart(Long.toString(START_TS)); + query.setQueries(subs); + query.validateAndSetQuery(); + + final Query[] compiled = query.buildQueries(tsdb); + results = new HashMap>( + compiled.length); + iterators = new HashMap(compiled.length); + + int index = 0; + for (final Query q : compiled) { + final DataPoints[] dps = q.runAsync().join(); + results.put(Integer.toString(index), + new Pair( + query.getQueries().get(index), dps)); + iterators.put(Integer.toString(index), + new TimeSyncedIterator(Integer.toString(index), + query.getQueries().get(index).getFilterTagKs(), dps)); + index++; + } + } + + /** + * A and B, each with two series. Common D values, different E values + */ + protected void twoSeriesAggedE() throws Exception { + setDataPointStorage(); + HashMap tags = new HashMap(2); + tags.put("D", "D"); + tags.put("E", "E"); + + tsdb.addPoint("A", 1431561600, 1, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561660, 2, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561720, 3, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "D"); + tags.put("E", "F"); + + tsdb.addPoint("A", 1431561600, 1, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561660, 2, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561720, 3, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "D"); + tags.put("E", "E"); + tsdb.addPoint("B", 1431561600, 11, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561660, 12, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561720, 13, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "D"); + tags.put("E", "F"); + tsdb.addPoint("B", 1431561600, 11, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561660, 12, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561720, 13, tags).joinUninterruptibly(); + } + + /** + * A and B, each with two series. Common D, different E values and the B metric + * series have an extra Z tag with different values. + */ + protected void twoSeriesAggedEandExtraTagK() throws Exception { + setDataPointStorage(); + HashMap tags = new HashMap(2); + tags.put("D", "D"); + tags.put("E", "E"); + + tsdb.addPoint("A", 1431561600, 1, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561660, 2, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561720, 3, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "D"); + tags.put("E", "F"); + + tsdb.addPoint("A", 1431561600, 1, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561660, 2, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561720, 3, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "D"); + tags.put("E", "E"); + tags.put("Z", "A"); + tsdb.addPoint("B", 1431561600, 11, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561660, 12, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561720, 13, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "D"); + tags.put("E", "F"); + tags.put("Z", "B"); + tsdb.addPoint("B", 1431561600, 11, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561660, 12, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561720, 13, tags).joinUninterruptibly(); + } + + /** + * A and B where A has two series and B only has one. Series A has two D + * values that will be agged. Different D and E values. + */ + protected void oneAggedTheOtherTagged() throws Exception { + setDataPointStorage(); + HashMap tags = new HashMap(2); + tags.put("D", "D"); + tags.put("E", "E"); + + tsdb.addPoint("A", 1431561600, 1, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561660, 2, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561720, 3, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "E"); + tags.put("E", "F"); + + tsdb.addPoint("A", 1431561600, 1, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561660, 2, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561720, 3, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "D"); + tags.put("E", "E"); + + tsdb.addPoint("B", 1431561600, 11, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561660, 12, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561720, 13, tags).joinUninterruptibly(); + } + + /** + * A only with three series. Different D values, commong E values. + */ + protected void threeSameENoB() throws Exception { + setDataPointStorage(); + HashMap tags = new HashMap(2); + tags.put("D", "D"); + tags.put("E", "E"); + + tsdb.addPoint("A", 1431561600, 1, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561660, 2, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561720, 3, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "F"); + tags.put("E", "E"); + tsdb.addPoint("A", 1431561600, 4, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561660, 5, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561720, 6, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "G"); + tags.put("E", "E"); + tsdb.addPoint("A", 1431561600, 7, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561660, 8, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561720, 9, tags).joinUninterruptibly(); + } + + /** + * A and B where A has two series, B has three. Different D values, common E. + */ + protected void oneExtraSameE() throws Exception { + setDataPointStorage(); + HashMap tags = new HashMap(2); + tags.put("D", "D"); + tags.put("E", "E"); + + tsdb.addPoint("A", 1431561600, 1, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561660, 2, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561720, 3, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "F"); + tags.put("E", "E"); + tsdb.addPoint("A", 1431561600, 4, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561660, 5, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561720, 6, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "D"); + tags.put("E", "E"); + tsdb.addPoint("B", 1431561600, 11, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561660, 12, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561720, 13, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "F"); + tags.put("E", "E"); + tsdb.addPoint("B", 1431561600, 14, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561660, 15, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561720, 16, tags).joinUninterruptibly(); + + // all by myself...... + tags = new HashMap(2); + tags.put("D", "G"); + tags.put("E", "E"); + tsdb.addPoint("B", 1431561600, 14, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561660, 15, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561720, 16, tags).joinUninterruptibly(); + } + + /** + * A and B with two series each. Different D values, common E. + * A has values at T0 and T1, but then B has values at T2 and T3. Should + * throw NaNs after the intersection. + */ + protected void timeOffset() throws Exception { + setDataPointStorage(); + HashMap tags = new HashMap(2); + tags.put("D", "D"); + tags.put("E", "E"); + + tsdb.addPoint("A", 1431561600, 1, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561660, 2, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "F"); + tags.put("E", "E"); + tsdb.addPoint("A", 1431561600, 4, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561660, 5, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "D"); + tags.put("E", "E"); + tsdb.addPoint("B", 1431561720, 13, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561780, 14, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "F"); + tags.put("E", "E"); + tsdb.addPoint("B", 1431561720, 16, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561780, 17, tags).joinUninterruptibly(); + } + + /** + * A and B, each with three series. Different D values, commong E. + */ + protected void threeSameE() throws Exception { + setDataPointStorage(); + HashMap tags = new HashMap(2); + tags.put("D", "D"); + tags.put("E", "E"); + + tsdb.addPoint("A", 1431561600, 1, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561660, 2, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561720, 3, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "F"); + tags.put("E", "E"); + tsdb.addPoint("A", 1431561600, 4, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561660, 5, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561720, 6, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "G"); + tags.put("E", "E"); + tsdb.addPoint("A", 1431561600, 7, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561660, 8, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561720, 9, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "D"); + tags.put("E", "E"); + tsdb.addPoint("B", 1431561600, 11, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561660, 12, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561720, 13, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "F"); + tags.put("E", "E"); + tsdb.addPoint("B", 1431561600, 14, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561660, 15, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561720, 16, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "G"); + tags.put("E", "E"); + tsdb.addPoint("B", 1431561600, 17, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561660, 18, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561720, 19, tags).joinUninterruptibly(); + } + + /** + * A and B, each with three series. Different D values. Series in A are missing + * the E tag. Common values in E for B. + */ + protected void threeAMissingE() throws Exception { + setDataPointStorage(); + HashMap tags = new HashMap(2); + tags.put("D", "D"); + tsdb.addPoint("A", 1431561600, 1, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561660, 2, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561720, 3, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "F"); + tsdb.addPoint("A", 1431561600, 4, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561660, 5, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561720, 6, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "G"); + tsdb.addPoint("A", 1431561600, 7, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561660, 8, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561720, 9, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "D"); + tags.put("E", "E"); + tsdb.addPoint("B", 1431561600, 11, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561660, 12, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561720, 13, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "F"); + tags.put("E", "E"); + tsdb.addPoint("B", 1431561600, 14, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561660, 15, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561720, 16, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "G"); + tags.put("E", "E"); + tsdb.addPoint("B", 1431561600, 17, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561660, 18, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561720, 19, tags).joinUninterruptibly(); + } + + /** + * A and B, each with three series. Different D and E values + */ + protected void threeDifE() throws Exception { + setDataPointStorage(); + HashMap tags = new HashMap(2); + tags.put("D", "D"); + tags.put("E", "A"); + tsdb.addPoint("A", 1431561600, 1, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561660, 2, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561720, 3, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "F"); + tags.put("E", "B"); + tsdb.addPoint("A", 1431561600, 4, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561660, 5, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561720, 6, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "G"); + tags.put("E", "C"); + tsdb.addPoint("A", 1431561600, 7, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561660, 8, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561720, 9, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "D"); + tags.put("E", "D"); + tsdb.addPoint("B", 1431561600, 11, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561660, 12, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561720, 13, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "F"); + tags.put("E", "F"); + tsdb.addPoint("B", 1431561600, 14, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561660, 15, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561720, 16, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "G"); + tags.put("E", "G"); + tsdb.addPoint("B", 1431561600, 17, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561660, 18, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561720, 19, tags).joinUninterruptibly(); + } + + /** + * A and B, each with 3 series. Different D values, common E. + * Each set has one series that isn't in the other. D=G in A and D=Q in B. + */ + protected void threeDisjointSameE() throws Exception { + setDataPointStorage(); + + HashMap tags = new HashMap(2); + tags.put("D", "D"); + tags.put("E", "E"); + tsdb.addPoint("A", 1431561600, 1, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561660, 2, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561720, 3, tags).joinUninterruptibly(); + + // not in set 2 + tags = new HashMap(2); + tags.put("D", "F"); + tags.put("E", "E"); + tsdb.addPoint("A", 1431561600, 4, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561660, 5, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561720, 6, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "G"); + tags.put("E", "E"); + tsdb.addPoint("A", 1431561600, 7, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561660, 8, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561720, 9, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "D"); + tags.put("E", "E"); + tsdb.addPoint("B", 1431561600, 11, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561660, 12, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561720, 13, tags).joinUninterruptibly(); + + // not in set 1 + tags = new HashMap(2); + tags.put("D", "Q"); + tags.put("E", "E"); + tsdb.addPoint("B", 1431561600, 14, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561660, 15, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561720, 16, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "G"); + tags.put("E", "E"); + tsdb.addPoint("B", 1431561600, 17, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561660, 18, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561720, 19, tags).joinUninterruptibly(); + } + + /** + * A and B, each with three series. Different D values, common E values. + * D=G is the only common series between the sets + */ + protected void reduceToOne() throws Exception { + setDataPointStorage(); + HashMap tags = new HashMap(2); + tags.put("D", "D"); + tags.put("E", "E"); + + tsdb.addPoint("A", 1431561600, 1, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561660, 2, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561720, 3, tags).joinUninterruptibly(); + + // not in set 2 + tags = new HashMap(2); + tags.put("D", "F"); + tags.put("E", "E"); + tsdb.addPoint("A", 1431561600, 4, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561660, 5, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561720, 6, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "G"); + tags.put("E", "E"); + tsdb.addPoint("A", 1431561600, 7, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561660, 8, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561720, 9, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "P"); + tags.put("E", "E"); + tsdb.addPoint("B", 1431561600, 11, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561660, 12, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561720, 13, tags).joinUninterruptibly(); + + // not in set 1 + tags = new HashMap(2); + tags.put("D", "Q"); + tags.put("E", "E"); + tsdb.addPoint("B", 1431561600, 14, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561660, 15, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561720, 16, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "G"); + tags.put("E", "E"); + tsdb.addPoint("B", 1431561600, 17, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561660, 18, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561720, 19, tags).joinUninterruptibly(); + } + + /** + * A and B, each with three series. Different D values, common E values. + * Each series is "missing" a data point so that we can test time sync. + */ + protected void threeSameEGaps() throws Exception { + setDataPointStorage(); + HashMap tags = new HashMap(2); + tags.put("D", "D"); + tags.put("E", "E"); + + tsdb.addPoint("A", 1431561600, 1, tags).joinUninterruptibly(); + //tsdb.addPoint("A", 1431561660, 2, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561720, 3, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "F"); + tags.put("E", "E"); + tsdb.addPoint("A", 1431561600, 4, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561660, 5, tags).joinUninterruptibly(); + //tsdb.addPoint("A", 1431561720, 6, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "G"); + tags.put("E", "E"); + //tsdb.addPoint("A", 1431561600, 7, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561660, 8, tags).joinUninterruptibly(); + tsdb.addPoint("A", 1431561720, 9, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "D"); + tags.put("E", "E"); + //tsdb.addPoint("B", 1431561600, 11, tags).joinUninterruptibly(); + //tsdb.addPoint("B", 1431561660, 12, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561720, 13, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "F"); + tags.put("E", "E"); + //tsdb.addPoint("B", 1431561600, 14, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561660, 15, tags).joinUninterruptibly(); + //tsdb.addPoint("B", 1431561720, 16, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "G"); + tags.put("E", "E"); + //tsdb.addPoint("B", 1431561600, 17, tags).joinUninterruptibly(); + //tsdb.addPoint("B", 1431561660, 18, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561720, 19, tags).joinUninterruptibly(); + } + +} diff --git a/test/query/expression/TestTimeSyncedIterator.java b/test/query/expression/TestTimeSyncedIterator.java new file mode 100644 index 0000000000..70491001f1 --- /dev/null +++ b/test/query/expression/TestTimeSyncedIterator.java @@ -0,0 +1,504 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.modules.junit4.PowerMockRunner; + +import net.opentsdb.core.DataPoint; +import net.opentsdb.core.FillPolicy; + +@RunWith(PowerMockRunner.class) +@PowerMockIgnore({"javax.management.*", "javax.xml.*", + "ch.qos.*", "org.slf4j.*", + "com.sum.*", "org.xml.*"}) +public class TestTimeSyncedIterator extends BaseTimeSyncedIteratorTest { + + @Test + public void ctor() throws Exception { + threeDisjointSameE(); + queryAB_Dstar(); + final TimeSyncedIterator it = new TimeSyncedIterator("0", + results.get("0").getKey().getFilterTagKs(), + results.get("0").getValue()); + + assertEquals(3, it.size()); + assertTrue(it.hasNext()); + assertEquals(0, it.getIndex()); + assertEquals("0", it.getId()); + assertEquals(results.get("0").getKey().getFilterTagKs(), it.getQueryTagKs()); + assertTrue(results.get("0").getValue() == it.getDataPoints()); + assertEquals(3, it.values().length); + assertEquals(1, it.getQueryTagKs().size()); + assertEquals(FillPolicy.ZERO, it.getFillPolicy().getPolicy()); + } + + @Test (expected = IllegalArgumentException.class) + public void ctorNullId() throws Exception { + threeDisjointSameE(); + queryAB_Dstar(); + new TimeSyncedIterator(null, + results.get("0").getKey().getFilterTagKs(), + results.get("0").getValue()); + } + + @Test (expected = IllegalArgumentException.class) + public void ctorEmptyId() throws Exception { + threeDisjointSameE(); + queryAB_Dstar(); + new TimeSyncedIterator("", + results.get("0").getKey().getFilterTagKs(), + results.get("0").getValue()); + } + + @Test + public void ctorNullQueryTags() throws Exception { + threeDisjointSameE(); + queryAB_Dstar(); + final TimeSyncedIterator it = new TimeSyncedIterator("0", null, + results.get("0").getValue()); + + assertEquals(3, it.size()); + assertTrue(it.hasNext()); + assertEquals(0, it.getIndex()); + assertEquals("0", it.getId()); + assertNull(it.getQueryTagKs()); + assertTrue(results.get("0").getValue() == it.getDataPoints()); + assertEquals(3, it.values().length); + } + + @Test (expected = IllegalArgumentException.class) + public void ctorNullDPs() throws Exception { + threeDisjointSameE(); + queryAB_Dstar(); + new TimeSyncedIterator("0", results.get("0").getKey().getFilterTagKs(), + null); + } + + @Test + public void threeSeries() throws Exception { + threeDisjointSameE(); + queryAB_Dstar(); + final TimeSyncedIterator it = new TimeSyncedIterator("0", + results.get("0").getKey().getFilterTagKs(), + results.get("0").getValue()); + + long ts = it.nextTimestamp(); + assertEquals(1431561600000L, ts); + + double[] values = new double[] { 1, 4, 7 }; + while (it.hasNext()) { + final DataPoint[] dps = it.next(ts); + assertEquals(3, dps.length); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(ts, dps[2].timestamp()); + assertEquals(values[0]++, dps[0].toDouble(), 0.0001); + assertEquals(values[1]++, dps[1].toDouble(), 0.0001); + assertEquals(values[2]++, dps[2].toDouble(), 0.0001); + ts = it.nextTimestamp(); + } + assertEquals(Long.MAX_VALUE, ts); + } + + @Test + public void threeSeriesEmitter() throws Exception { + threeDisjointSameE(); + queryAB_Dstar(); + final TimeSyncedIterator it = new TimeSyncedIterator("0", + results.get("0").getKey().getFilterTagKs(), + results.get("0").getValue()); + + long ts = it.nextTimestamp(); + assertEquals(1431561600000L, ts); + final DataPoint[] dps = it.values(); + double[] values = new double[] { 1, 4, 7 }; + while (it.hasNext()) { + it.next(ts); + assertEquals(3, dps.length); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(ts, dps[2].timestamp()); + assertEquals(values[0]++, dps[0].toDouble(), 0.0001); + assertEquals(values[1]++, dps[1].toDouble(), 0.0001); + assertEquals(values[2]++, dps[2].toDouble(), 0.0001); + ts = it.nextTimestamp(); + } + assertEquals(Long.MAX_VALUE, ts); + } + + @Test + public void nullASeries() throws Exception { + threeDisjointSameE(); + queryAB_Dstar(); + final TimeSyncedIterator it = new TimeSyncedIterator("0", + results.get("0").getKey().getFilterTagKs(), + results.get("0").getValue()); + // mimic the intersector kicking out a series + it.nullIterator(0); + + long ts = it.nextTimestamp(); + assertEquals(1431561600000L, ts); + + final DataPoint[] dps = it.values(); + double[] values = new double[] { 1, 4, 7 }; + while (it.hasNext()) { + it.next(ts); + assertEquals(3, dps.length); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(ts, dps[2].timestamp()); + assertEquals(0, dps[0].toDouble(), 0.0001); + assertEquals(values[1]++, dps[1].toDouble(), 0.0001); + assertEquals(values[2]++, dps[2].toDouble(), 0.0001); + ts = it.nextTimestamp(); + } + assertEquals(Long.MAX_VALUE, ts); + } + + @Test + public void nullAll() throws Exception { + threeDisjointSameE(); + queryAB_Dstar(); + final TimeSyncedIterator it = new TimeSyncedIterator("0", + results.get("0").getKey().getFilterTagKs(), + results.get("0").getValue()); + // mimic the intersector kicking out all series. + it.nullIterator(0); + it.nullIterator(1); + it.nullIterator(2); + + assertEquals(Long.MAX_VALUE, it.nextTimestamp()); + assertFalse(it.hasNext()); + } + + @Test + public void singleSeries() throws Exception { + threeDisjointSameE(); + queryA_DD(); + final TimeSyncedIterator it = new TimeSyncedIterator("0", + results.get("0").getKey().getFilterTagKs(), + results.get("0").getValue()); + + long ts = it.nextTimestamp(); + assertEquals(1431561600000L, ts); + + double value = 1; + while (it.hasNext()) { + final DataPoint[] dps = it.next(ts); + assertEquals(1, dps.length); + assertEquals(ts, dps[0].timestamp()); + assertEquals(value++, dps[0].toDouble(), 0.0001); + ts = it.nextTimestamp(); + } + assertEquals(Long.MAX_VALUE, ts); + } + + @Test + public void noData() throws Exception { + setDataPointStorage(); + queryA_DD(); + final TimeSyncedIterator it = new TimeSyncedIterator("0", + results.get("0").getKey().getFilterTagKs(), + results.get("0").getValue()); + + assertEquals(0, it.values().length); + assertEquals(Long.MAX_VALUE, it.nextTimestamp()); + assertFalse(it.hasNext()); + } + + @Test + public void threeSeriesMissing() throws Exception { + threeSameEGaps(); + queryAB_Dstar(); + final TimeSyncedIterator it = new TimeSyncedIterator("0", + results.get("0").getKey().getFilterTagKs(), + results.get("0").getValue()); + it.setFillPolicy(new NumericFillPolicy(FillPolicy.NOT_A_NUMBER)); + + long ts = it.nextTimestamp(); + assertEquals(1431561600000L, ts); + + DataPoint[] dps = it.next(ts); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(ts, dps[2].timestamp()); + + assertEquals(1, dps[0].toDouble(), 0.0001); + assertEquals(4, dps[1].toDouble(), 0.0001); + assertTrue(Double.isNaN(dps[2].toDouble())); + ts = it.nextTimestamp(); + + dps = it.next(ts); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(ts, dps[2].timestamp()); + + assertTrue(Double.isNaN(dps[0].toDouble())); + assertEquals(5, dps[1].toDouble(), 0.0001); + assertEquals(8, dps[2].toDouble(), 0.0001); + ts = it.nextTimestamp(); + + dps = it.next(ts); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(ts, dps[2].timestamp()); + + assertEquals(3, dps[0].toDouble(), 0.0001); + assertTrue(Double.isNaN(dps[1].toDouble())); + assertEquals(9, dps[2].toDouble(), 0.0001); + ts = it.nextTimestamp(); + } + + @Test + public void threeSeriesMissingFillZero() throws Exception { + threeSameEGaps(); + queryAB_Dstar(); + final TimeSyncedIterator it = new TimeSyncedIterator("0", + results.get("0").getKey().getFilterTagKs(), + results.get("0").getValue()); + it.setFillPolicy(new NumericFillPolicy(FillPolicy.ZERO)); + + long ts = it.nextTimestamp(); + assertEquals(1431561600000L, ts); + + DataPoint[] dps = it.next(ts); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(ts, dps[2].timestamp()); + + assertEquals(1, dps[0].toDouble(), 0.0001); + assertEquals(4, dps[1].toDouble(), 0.0001); + assertEquals(0, dps[2].toDouble(), 0.0001); + ts = it.nextTimestamp(); + + dps = it.next(ts); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(ts, dps[2].timestamp()); + + assertEquals(0, dps[0].toDouble(), 0.0001); + assertEquals(5, dps[1].toDouble(), 0.0001); + assertEquals(8, dps[2].toDouble(), 0.0001); + ts = it.nextTimestamp(); + + dps = it.next(ts); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(ts, dps[2].timestamp()); + + assertEquals(3, dps[0].toDouble(), 0.0001); + assertEquals(0, dps[1].toDouble(), 0.0001); + assertEquals(9, dps[2].toDouble(), 0.0001); + ts = it.nextTimestamp(); + } + + @Test + public void threeSeriesMissingNull() throws Exception { + threeSameEGaps(); + queryAB_Dstar(); + final TimeSyncedIterator it = new TimeSyncedIterator("0", + results.get("0").getKey().getFilterTagKs(), + results.get("0").getValue()); + it.setFillPolicy(new NumericFillPolicy(FillPolicy.NULL)); + + long ts = it.nextTimestamp(); + assertEquals(1431561600000L, ts); + + DataPoint[] dps = it.next(ts); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(ts, dps[2].timestamp()); + + assertEquals(1, dps[0].toDouble(), 0.0001); + assertEquals(4, dps[1].toDouble(), 0.0001); + assertTrue(Double.isNaN(dps[2].toDouble())); + ts = it.nextTimestamp(); + + dps = it.next(ts); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(ts, dps[2].timestamp()); + + assertTrue(Double.isNaN(dps[0].toDouble())); + assertEquals(5, dps[1].toDouble(), 0.0001); + assertEquals(8, dps[2].toDouble(), 0.0001); + ts = it.nextTimestamp(); + + dps = it.next(ts); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(ts, dps[2].timestamp()); + + assertEquals(3, dps[0].toDouble(), 0.0001); + assertTrue(Double.isNaN(dps[1].toDouble())); + assertEquals(9, dps[2].toDouble(), 0.0001); + ts = it.nextTimestamp(); + } + + @Test + public void threeSeriesMissingScalar() throws Exception { + threeSameEGaps(); + queryAB_Dstar(); + final TimeSyncedIterator it = new TimeSyncedIterator("0", + results.get("0").getKey().getFilterTagKs(), + results.get("0").getValue()); + it.setFillPolicy(new NumericFillPolicy(FillPolicy.SCALAR, 42)); + + long ts = it.nextTimestamp(); + assertEquals(1431561600000L, ts); + + DataPoint[] dps = it.next(ts); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(ts, dps[2].timestamp()); + + assertEquals(1, dps[0].toDouble(), 0.0001); + assertEquals(4, dps[1].toDouble(), 0.0001); + assertEquals(42, dps[2].toDouble(), 0.0001); + ts = it.nextTimestamp(); + + dps = it.next(ts); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(ts, dps[2].timestamp()); + + assertEquals(42, dps[0].toDouble(), 0.0001); + assertEquals(5, dps[1].toDouble(), 0.0001); + assertEquals(8, dps[2].toDouble(), 0.0001); + ts = it.nextTimestamp(); + + dps = it.next(ts); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(ts, dps[2].timestamp()); + + assertEquals(3, dps[0].toDouble(), 0.0001); + assertEquals(42, dps[1].toDouble(), 0.0001); + assertEquals(9, dps[2].toDouble(), 0.0001); + ts = it.nextTimestamp(); + } + + @Test + public void failToNextTS() throws Exception { + threeDisjointSameE(); + queryAB_Dstar(); + final TimeSyncedIterator it = new TimeSyncedIterator("0", + results.get("0").getKey().getFilterTagKs(), + results.get("0").getValue()); + + long ts = it.nextTimestamp(); + assertEquals(1431561600000L, ts); + + int i = 0; + boolean broke = false; + while (it.hasNext()) { + // in this case the caller fails to advance the TS so we keep getting the + // same data points over and over again. + it.next(ts); + ++i; + if (i > 100) { + broke = true; + break; + } + } + if (!broke) { + fail("Expected to iterate over 100 times"); + } + } + + @Test + public void nextTimestampDoesntAdvance() throws Exception { + threeDisjointSameE(); + queryAB_Dstar(); + final TimeSyncedIterator it = new TimeSyncedIterator("0", + results.get("0").getKey().getFilterTagKs(), + results.get("0").getValue()); + + assertEquals(1431561600000L, it.nextTimestamp()); + assertEquals(1431561600000L, it.nextTimestamp()); + assertEquals(1431561600000L, it.nextTimestamp()); + assertEquals(1431561600000L, it.nextTimestamp()); + assertEquals(1431561600000L, it.nextTimestamp()); + assertEquals(1431561600000L, it.nextTimestamp()); + assertEquals(1431561600000L, it.nextTimestamp()); + } + + @Test + public void nextExceptionNoException() throws Exception { + threeDisjointSameE(); + queryAB_Dstar(); + final TimeSyncedIterator it = new TimeSyncedIterator("0", + results.get("0").getKey().getFilterTagKs(), + results.get("0").getValue()); + it.next(it.nextTimestamp()); + it.next(it.nextTimestamp()); + it.next(it.nextTimestamp()); + it.next(it.nextTimestamp()); + assertEquals(Long.MAX_VALUE, it.nextTimestamp()); + } + + @Test + public void getCopy() throws Exception { + threeDisjointSameE(); + queryAB_Dstar(); + final TimeSyncedIterator it = new TimeSyncedIterator("0", + results.get("0").getKey().getFilterTagKs(), + results.get("0").getValue()); + + final ITimeSyncedIterator copy = it.getCopy(); + assertTrue(copy != it); + + long ts = it.nextTimestamp(); + assertEquals(1431561600000L, ts); + + double[] values = new double[] { 1, 4, 7 }; + while (it.hasNext()) { + final DataPoint[] dps = it.next(ts); + assertEquals(3, dps.length); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(ts, dps[2].timestamp()); + assertEquals(values[0]++, dps[0].toDouble(), 0.0001); + assertEquals(values[1]++, dps[1].toDouble(), 0.0001); + assertEquals(values[2]++, dps[2].toDouble(), 0.0001); + ts = it.nextTimestamp(); + } + assertEquals(Long.MAX_VALUE, ts); + + ts = copy.nextTimestamp(); + assertEquals(1431561600000L, ts); + + values = new double[] { 1, 4, 7 }; + while (copy.hasNext()) { + final DataPoint[] dps = copy.next(ts); + assertEquals(3, dps.length); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(ts, dps[2].timestamp()); + assertEquals(values[0]++, dps[0].toDouble(), 0.0001); + assertEquals(values[1]++, dps[1].toDouble(), 0.0001); + assertEquals(values[2]++, dps[2].toDouble(), 0.0001); + ts = copy.nextTimestamp(); + } + assertEquals(Long.MAX_VALUE, ts); + } +} From a27c81961249746d08e52003382eedbd7934190c Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Thu, 5 Nov 2015 12:04:46 -0800 Subject: [PATCH 064/233] Add the VariableIterator class that provides an interface for set operations and interation. Signed-off-by: Chris Larsen --- Makefile.am | 3 +- src/query/expression/VariableIterator.java | 101 +++++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 src/query/expression/VariableIterator.java diff --git a/Makefile.am b/Makefile.am index 0ce731dab0..d9fba4272f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -89,12 +89,13 @@ tsdb_SRC := \ src/query/expression/PostAggregatedDataPoints.java \ src/query/expression/Scale.java \ src/query/expression/TimeSyncedIterator.java \ + src/query/expression/TimeSyncedIterator.java \ src/query/filter/TagVFilter.java \ src/query/filter/TagVLiteralOrFilter.java \ src/query/filter/TagVNotKeyFilter.java \ src/query/filter/TagVNotLiteralOrFilter.java \ src/query/filter/TagVRegexFilter.java \ - src/query/filter/TagVWildcardFilter.java \ + src/query/filter/VariableIterator.java \ src/search/SearchPlugin.java \ src/search/SearchQuery.java \ src/search/TimeSeriesLookup.java \ diff --git a/src/query/expression/VariableIterator.java b/src/query/expression/VariableIterator.java new file mode 100644 index 0000000000..0ed7c6dab8 --- /dev/null +++ b/src/query/expression/VariableIterator.java @@ -0,0 +1,101 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * An interface that helps merge different time series sets (e.g. different + * metrics with a group by operator). The implementations handle joining the + * two sets according to the {@link SetOperator}. + * @since 2.3 + */ +public interface VariableIterator { + + /** An operator that determines how to sets of time series are merged via + * expression. */ + public enum SetOperator { + /** A union, meaning results from all sets will appear, using FillPolicies + * for missing series */ + UNION("union"), + + /** Computes the intersection, returning results only for series that appear + * in all sets */ + INTERSECTION("intersection"); + + /** The user-friendly name of this operator. */ + private final String name; + + /** @param the readable name of the operator */ + SetOperator(final String name) { + this.name = name; + } + + /** @return the readable name of the operator */ + @JsonValue + public String getName() { + return name; + } + + /** + * Converts a string to lower case then looks up the operator + * @param name The name to find an operator for + * @return The operator if found. + * @throws IllegalArgumentException if the operator wasn't found + */ + @JsonCreator + public static SetOperator fromString(final String name) { + for (final SetOperator operator : SetOperator.values()) { + if (operator.name.equalsIgnoreCase(name)) { + return operator; + } + } + throw new IllegalArgumentException("Unrecognized set operator: " + name); + } + } + + /** + * Whether or not another set of results are available. Always call this + * before calling next. + * @return True if more results are available, false if not. + */ + public boolean hasNext(); + + /** + * Iterates the {@link getResults()} to the next set of results. If there + * aren't any results left, the implementation may throw an exception. Always + * call {@link hasNext()} first. + */ + public void next(); + + /** + * Returns a map of variable names to result series. You can maintain the + * reference returned without having to call getResults() on every iteration. + * Calling {@link next()} will simply update the ExpressionDataPoint array. + * The implementation may return a null map if there weren't any results + * available. Always all {@link hasNext()} before getting the results. + * @return A map with results to read from. + */ + public Map getResults(); + + /** @return The number of time series after the join. This should match the + * number of entries in the results data point array, not the number of + * variables in the results map. */ + public int getSeriesSize(); + + /** @return the next timestamp for all results without iterating */ + public long nextTimestamp(); +} From f0286ea766bb5ca22d094a8402f8aca6d0174b49 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Thu, 5 Nov 2015 12:19:06 -0800 Subject: [PATCH 065/233] Add the IntersectionIterator for computing set unions based on the intersection of sets, i.e. only return results that have series in every set, kicking out any series that are missing in another set. Signed-off-by: Chris Larsen --- Makefile.am | 2 + .../expression/IntersectionIterator.java | 500 +++++++++++ .../expression/TestIntersectionIterator.java | 813 ++++++++++++++++++ 3 files changed, 1315 insertions(+) create mode 100644 src/query/expression/IntersectionIterator.java create mode 100644 test/query/expression/TestIntersectionIterator.java diff --git a/Makefile.am b/Makefile.am index d9fba4272f..84f76540bf 100644 --- a/Makefile.am +++ b/Makefile.am @@ -83,6 +83,7 @@ tsdb_SRC := \ src/query/expression/ExpressionTree.java \ src/query/expression/HighestCurrent.java \ src/query/expression/HighestMax.java \ + src/query/expression/IntersectionIterator.java \ src/query/expression/ITimeSyncedIterator.java \ src/query/expression/NumericFillPolicy.java \ src/query/expression/MovingAverage.java \ @@ -254,6 +255,7 @@ test_SRC := \ test/query/expression/TestExpressionTree.java \ test/query/expression/TestHighestCurrent.java \ test/query/expression/TestHighestMax.java \ + test/query/expression/TestIntersectionIterator.java \ test/query/expression/TestNumericFillPolicy.java \ test/query/expression/TestMovingAverage.java \ test/query/expression/TestPostAggregatedDataPoints.java \ diff --git a/src/query/expression/IntersectionIterator.java b/src/query/expression/IntersectionIterator.java new file mode 100644 index 0000000000..99e1e3ae7c --- /dev/null +++ b/src/query/expression/IntersectionIterator.java @@ -0,0 +1,500 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +import net.opentsdb.core.IllegalDataException; +import net.opentsdb.core.TSDB; +import net.opentsdb.utils.ByteSet; + +import org.hbase.async.Bytes; +import org.hbase.async.Bytes.ByteMap; +import org.hbase.async.HBaseClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sun.reflect.generics.reflectiveObjects.NotImplementedException; + +/** + * This class handles taking a set of queries and their results and iterates + * over each series in each set with time alignment after computing the + * intersection of all sets. + *

+ * The iterator performs the following: + * - calculates the intersection of all queries based on the tags or query tags + * and optionally the aggregated tags. + * - any series that are not members of ever set are kicked out (and logged). + * - series are aligned across queries so that expressions can operate over them. + * - series are also time aligned and maintain alignment during iteration. + *

+ * The {@link #current_values} map will map the expression "variables" to the + * proper iterator for each serie's array. E.g. + * <"A", [1, 2, 3, 4]> + * <"B", [1, 2, 3, 4]> + *

+ * So to use it's you simply fetch the result map, call {@link #hasNext()} and + * {@link #next()} to iterate and in a for loop, iterate {@link #getSeriesSize()} + * times to get all of the current values. + * For efficiency, call {@link #getResults()} once before iterating, then on + * each call to {@link #next()} you can just iterate over the same result map + * again as the values will be updated. + * @since 2.3 + */ +public class IntersectionIterator implements ITimeSyncedIterator, VariableIterator { + private static final Logger LOG = LoggerFactory.getLogger(IntersectionIterator.class); + + /** The queries compiled and fetched from storage */ + private final Map queries; + + /** A list of the current values for each series post intersection */ + private final Map current_values; + + /** A map of the sub query index to their names for intersection computation */ + private final String[] index_to_names; + + /** Whether or not to intersect on the query tagks instead of the result set + * tagks */ + private final boolean intersect_on_query_tagks; + + /** Whether or not to include the aggregated tags in the result set */ + private final boolean include_agg_tags; + + /** The start/current timestamp for the iterator in ms */ + private long timestamp; + + /** Post intersection number of time series */ + private int series_size; + + /** The ID of this iterator */ + private final String id; + + /** The index of this iterator in a list of iterators */ + private int index; + + /** + * Ctor to create the expression lock-step iterator from a set of query results. + * If the results map is empty, then the ctor will complete but the results map + * will be empty and calls to {@link #hasNext()} will always return false. + * @param results The query results to store + * @param intersect_on_query_tagks Whether or not to include only the query + * specified tags during intersection + * @param include_agg_tags Whether or not to include aggregated tags during + * intersection + * @throws IllegalDataException if, after computing the intersection, no results + * would be left. + */ + public IntersectionIterator(final String id, final Map results, + final boolean intersect_on_query_tagks, final boolean include_agg_tags) { + this.id = id; + this.intersect_on_query_tagks = intersect_on_query_tagks; + this.include_agg_tags = include_agg_tags; + timestamp = Long.MAX_VALUE; + queries = new HashMap(results.size()); + current_values = new HashMap(results.size()); + index_to_names = new String[results.size()]; + + int max_series = 0; + int i = 0; + for (final Map.Entry entry : results.entrySet()) { + if (LOG.isDebugEnabled()) { + LOG.debug("Adding iterator " + entry.getValue()); + } + queries.put(entry.getKey(), entry.getValue()); + entry.getValue().setIndex(i); + index_to_names[i] = entry.getKey(); + if (entry.getValue().values().length > max_series) { + max_series = entry.getValue().values().length; + } + ++i; + } + + if (max_series < 1) { + // we don't want to throw an exception here, just set it up so that the + // call to {@link #hasNext()} will be false. + LOG.debug("No series in the result sets"); + return; + } + + computeIntersection(); + + // calculate the starting timestamp from the various iterators + for (final ITimeSyncedIterator it : queries.values()) { + final long ts = it.nextTimestamp(); + if (ts < timestamp) { + timestamp = ts; + } + } + } + + /** + * A sort of copy constructor that populates the iterator from an existing + * iterator, copying all child iterators. + * @param iterator The iterator to copy from. + */ + private IntersectionIterator(final IntersectionIterator iterator) { + id = iterator.id; + intersect_on_query_tagks = iterator.intersect_on_query_tagks; + include_agg_tags = iterator.include_agg_tags; + timestamp = Long.MAX_VALUE; + queries = new HashMap(iterator.queries.size()); + current_values = new HashMap(queries.size()); + index_to_names = new String[queries.size()]; + + int max_series = 0; + int i = 0; + for (final Entry entry : iterator.queries.entrySet()) { + queries.put(entry.getKey(), entry.getValue().getCopy()); + entry.getValue().setIndex(i); + index_to_names[i] = entry.getKey(); + if (entry.getValue().values().length > max_series) { + max_series = entry.getValue().values().length; + } + ++i; + } + + if (max_series < 1) { + // we don't want to throw an exception here, just set it up so that the + // call to {@link #hasNext()} will be false. + LOG.debug("No series in the result sets"); + return; + } + + computeIntersection(); + + // calculate the starting timestamp from the various iterators + for (final ITimeSyncedIterator it : queries.values()) { + final long ts = it.nextTimestamp(); + if (ts < timestamp) { + timestamp = ts; + } + } + } + + @Override + public String toString() { + final StringBuilder buf = new StringBuilder(); + buf.append("IntersectionIterator(id=") + .append(id) + .append(", useQueryTags=") + .append(intersect_on_query_tagks) + .append(", includeAggTags=") + .append(include_agg_tags) + .append(", index=") + .append(index) + .append(", queries=") + .append(queries); + return buf.toString(); + } + + @Override + public boolean hasNext() { + for (final ITimeSyncedIterator sub : queries.values()) { + if (sub.hasNext()) { + return true; + } + } + return false; + } + + /** fetch the next set of time aligned results for all series */ + @Override + public void next() { + if (!hasNext()) { + throw new IllegalDataException("No more data"); + } + for (final ITimeSyncedIterator sub : queries.values()) { + sub.next(timestamp); + } + timestamp = nextTimestamp(); + } + + /** @return a map of values that will change on each iteration */ + @Override + public Map getResults() { + return current_values; + } + + /** @return the number of series in each map of the result set */ + @Override + public int getSeriesSize() { + return series_size; + } + + /** @return the next timestamp calculated from all series in the set */ + public long nextTimestamp() { + long ts = Long.MAX_VALUE; + for (final ITimeSyncedIterator sub : queries.values()) { + if (sub != null) { + final long t = sub.nextTimestamp(); + if (t < ts) { + ts = t; + } + } + } + return ts; + } + + /** + * A super ugly messy way to compute the intersection of the various sets of + * time series returned from the sub queries. + *

+ * The process is: + * - Iterate over each query set + * - For the first set, flatten each series' tag and (optionally) aggregated tag + * set into a single byte array for use as an ID. + * - Populate a map with the IDs and references to the series iterator for the + * first query set. + * - For each additional set, flatten the tags and if the tag set ID isn't in + * the intersection map, kick it out. + * - For each key in the intersection map, if it doesn't appear in the current + * query set, kick it out. + * - Once all sets are finished, align the resulting series iterators in the + * {@link #current_values} map which is then prepped for expression processing. + * @throws IllegalDataException if more than one series was supplied and + * the resulting intersection failed to produce any series + */ + private void computeIntersection() { + final ByteMap ordered_intersection = + new ByteMap(); + final Iterator it = queries.values().iterator(); + + // assume we have at least on query in our set + ITimeSyncedIterator sub = it.next(); + Map> flattened_tags = + new HashMap>(queries.size()); + ByteMap tags = new ByteMap(); + flattened_tags.put(sub.getId(), tags); + ExpressionDataPoint[] dps = sub.values(); + + for (int i = 0; i < sub.size(); i++) { + final byte[] tagks = flattenTags(intersect_on_query_tagks, include_agg_tags, + dps[i].tags(), dps[i].aggregatedTags(), sub); + tags.put(tagks, i); + + final ExpressionDataPoint[] idps = new ExpressionDataPoint[queries.size()]; + idps[sub.getIndex()] = dps[i]; + ordered_intersection.put(tagks, idps); + } + + if (!it.hasNext()) { + setCurrentAndMeta(ordered_intersection); + return; + } + + while (it.hasNext()) { + sub = it.next(); + tags = new ByteMap(); + flattened_tags.put(sub.getId(), tags); + dps = sub.values(); + + // loop through the series in the sub iterator, compute the flattened tag + // ids, then kick out any that are NOT in the existing intersection map. + for (int i = 0; i < sub.size(); i++) { + final byte[] tagks = flattenTags(intersect_on_query_tagks, include_agg_tags, + dps[i].tags(), dps[i].aggregatedTags(), sub); + tags.put(tagks, i); + + final ExpressionDataPoint[] idps = ordered_intersection.get(tagks); + if (idps == null) { + if (LOG.isDebugEnabled()) { + LOG.debug("Kicking out " + Bytes.pretty(tagks) + " from " + sub.getId()); + } + sub.nullIterator(i); + continue; + } + idps[sub.getIndex()] = dps[i]; + } + + // gotta go backwards now to complete the intersection by kicking + // any series that appear in other sets but not HERE + final Iterator> reverse_it = + ordered_intersection.iterator(); + while (reverse_it.hasNext()) { + Entry e = reverse_it.next(); + if (!tags.containsKey(e.getKey())) { + if (LOG.isDebugEnabled()) { + LOG.debug("Kicking out " + Bytes.pretty(e.getKey()) + + " from the main list since the query for " + sub.getId() + + " didn't have it"); + } + + // null the iterators for the other sets + for (final Map.Entry> entry : + flattened_tags.entrySet()) { + if (entry.getKey().equals(sub.getId())) { + continue; + } + final Integer index = entry.getValue().get(e.getKey()); + if (index != null) { + queries.get(entry.getKey()).nullIterator(index); + } + } + + reverse_it.remove(); + } + } + } + + // now set our properly condensed and ordered values + if (ordered_intersection.size() < 1) { + // TODO - is it best to toss an exception here or return an empty result? + throw new IllegalDataException("No intersections found: " + this); + } + + setCurrentAndMeta(ordered_intersection); + } + + /** + * Takes the resulting intersection and builds the {@link #current_values} + * and {@link #meta} maps. + * @param ordered_intersection The intersection to build from. + */ + private void setCurrentAndMeta(final ByteMap + ordered_intersection) { + for (final String id : queries.keySet()) { + current_values.put(id, new ExpressionDataPoint[ordered_intersection.size()]); + } + + int i = 0; + for (final ExpressionDataPoint[] idps : ordered_intersection.values()) { + for (int x = 0; x < idps.length; x++) { + final ExpressionDataPoint[] current_dps = + current_values.get(index_to_names[x]); + current_dps[i] = idps[x]; + } + ++i; + } + series_size = ordered_intersection.size(); + } + + /** + * Flattens the appropriate tags into a single byte array + * @param use_query_tags Whether or not to include tags returned with the + * results or just use those group by'd in the query + * @param include_agg_tags Whether or not to include the aggregated tags in + * the identifier + * @param tags The map of tags from the result set + * @param agg_tags The list of aggregated tags + * @param sub The sub query iterator + * @return A byte array with the flattened tag keys and values. Note that + * if the tags set is empty, this may return an empty array (but not a null + * array) + */ + static byte[] flattenTags(final boolean use_query_tags, + final boolean include_agg_tags, final ByteMap tags, + final ByteSet agg_tags, final ITimeSyncedIterator sub) { + if (tags.isEmpty()) { + return HBaseClient.EMPTY_ARRAY; + } + final ByteSet query_tagks; + // NOTE: We MAY need the agg tags but I'm not sure yet + final int tag_size; + if (use_query_tags) { + int i = 0; + if (sub.getQueryTagKs() != null && !sub.getQueryTagKs().isEmpty()) { + query_tagks = sub.getQueryTagKs(); + for (final Map.Entry pair : tags.entrySet()) { + if (query_tagks.contains(pair.getKey())) { + i++; + } + } + } else { + query_tagks = new ByteSet(); + } + tag_size = i; + } else { + query_tagks = new ByteSet(); + tag_size = tags.size(); + } + + int len = (tag_size * (TSDB.tagk_width() + TSDB.tagv_width())) + + (include_agg_tags ? (agg_tags.size() * TSDB.tagk_width()) : 0); + final byte[] tagks = new byte[len]; + int i = 0; + for (final Map.Entry pair : tags.entrySet()) { + if (use_query_tags && !query_tagks.contains(pair.getKey())) { + continue; + } + System.arraycopy(pair.getKey(), 0, tagks, i, TSDB.tagk_width()); + i += TSDB.tagk_width(); + System.arraycopy(pair.getValue(), 0, tagks, i, TSDB.tagv_width()); + i += TSDB.tagv_width(); + } + if (include_agg_tags) { + for (final byte[] tagk : agg_tags) { + System.arraycopy(tagk, 0, tagks, i, TSDB.tagk_width()); + i += TSDB.tagk_width(); + } + } + return tagks; + } + + @Override + public ExpressionDataPoint[] next(long timestamp) { + throw new NotImplementedException(); + } + + @Override + public int size() { + throw new NotImplementedException(); + } + + @Override + public ExpressionDataPoint[] values() { + throw new NotImplementedException(); + } + + @Override + public void nullIterator(int index) { + throw new NotImplementedException(); + } + + @Override + public int getIndex() { + return index; + } + + @Override + public void setIndex(int index) { + this.index = index; + } + + @Override + public String getId() { + return id; + } + + @Override + public ByteSet getQueryTagKs() { + throw new NotImplementedException(); + } + + @Override + public void setFillPolicy(NumericFillPolicy policy) { + throw new NotImplementedException(); + } + + @Override + public NumericFillPolicy getFillPolicy() { + throw new NotImplementedException(); + } + + @Override + public ITimeSyncedIterator getCopy() { + return new IntersectionIterator(this); + } +} diff --git a/test/query/expression/TestIntersectionIterator.java b/test/query/expression/TestIntersectionIterator.java new file mode 100644 index 0000000000..80adf6ca43 --- /dev/null +++ b/test/query/expression/TestIntersectionIterator.java @@ -0,0 +1,813 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.HashMap; +import java.util.Map; + +import org.hbase.async.HBaseClient; +import org.hbase.async.Bytes.ByteMap; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import net.opentsdb.core.DataPoint; +import net.opentsdb.core.FillPolicy; +import net.opentsdb.core.IllegalDataException; +import net.opentsdb.storage.MockBase; +import net.opentsdb.utils.ByteSet; + +@RunWith(PowerMockRunner.class) +@PowerMockIgnore({"javax.management.*", "javax.xml.*", + "ch.qos.*", "org.slf4j.*", + "com.sum.*", "org.xml.*"}) +@PrepareForTest({ TimeSyncedIterator.class }) +public class TestIntersectionIterator extends BaseTimeSyncedIteratorTest { + + /** used for the flattenTags tests */ + private static final byte[] UID1 = new byte[] { 0, 0, 1 }; + private static final byte[] UID2 = new byte[] { 0, 0, 2 }; + private static final byte[] UID3 = new byte[] { 0, 0, 3 }; + private ByteMap tags; + private ByteSet agg_tags; + private ITimeSyncedIterator sub; + private ByteSet query_tags; + + @Before + public void beforeLocal() throws Exception { + tags = new ByteMap(); + tags.put(UID1, UID1); + tags.put(UID2, UID2); + + agg_tags = new ByteSet(); + agg_tags.add(UID3); + + sub = mock(ITimeSyncedIterator.class); + query_tags = new ByteSet(); + query_tags.add(UID1); + when(sub.getQueryTagKs()).thenReturn(query_tags); + } + + @Test (expected = NullPointerException.class) + public void ctorNullResults() { + new IntersectionIterator("it", null, true, true); + } + + @Test + public void ctorEmptyResults() { + final IntersectionIterator it = new IntersectionIterator("it", + new HashMap(), true, true); + assertEquals(0, it.getSeriesSize()); + assertFalse(it.hasNext()); + } + + @Test + public void twoAndThreeSeries() throws Exception { + oneExtraSameE(); + queryAB_Dstar(); + + final IntersectionIterator it = new IntersectionIterator("it", iterators, false, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(2, it.getSeriesSize()); + + long ts = 1431561600000L; + double values[] = new double[] { 1, 4, 11, 14 }; + while (it.hasNext()) { + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(2, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(values[0]++, set_dps[0].toDouble(), 0.0001); + assertEquals(values[1]++, set_dps[1].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(2, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(values[2]++, set_dps[0].toDouble(), 0.0001); + assertEquals(values[3]++, set_dps[1].toDouble(), 0.0001); + + ts += 60000; + } + } + + @Test + public void twoAndThreeSeriesExtraDPinKickedSeries() throws Exception { + // in this case we want to make sure the kicked series doesn't cause us + // to dump a bunch of nulls + oneExtraSameE(); + queryAB_Dstar(); + + final HashMap tags = new HashMap(2); + tags.put("D", "G"); + tags.put("E", "E"); + tsdb.addPoint("B", 1431561630, 14, tags).joinUninterruptibly(); + + final IntersectionIterator it = new IntersectionIterator("it", iterators, false, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(2, it.getSeriesSize()); + + long ts = 1431561600000L; + double values[] = new double[] { 1, 4, 11, 14 }; + while (it.hasNext()) { + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(2, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(values[0]++, set_dps[0].toDouble(), 0.0001); + assertEquals(values[1]++, set_dps[1].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(2, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(values[2]++, set_dps[0].toDouble(), 0.0001); + assertEquals(values[3]++, set_dps[1].toDouble(), 0.0001); + + ts += 60000; + } + } + + @Test + public void threeSeriesIntersectToTwo() throws Exception { + threeDisjointSameE(); + queryAB_Dstar(); + + final IntersectionIterator it = new IntersectionIterator("it", iterators, false, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(2, it.getSeriesSize()); + + long ts = 1431561600000L; + double values[] = new double[] { 1, 7, 11, 17 }; + while (it.hasNext()) { + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(2, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(values[0]++, set_dps[0].toDouble(), 0.0001); + assertEquals(values[1]++, set_dps[1].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(2, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(values[2]++, set_dps[0].toDouble(), 0.0001); + assertEquals(values[3]++, set_dps[1].toDouble(), 0.0001); + + ts += 60000; + } + } + + @Test + public void threeSeriesIntersectToExtraDPsinKicked() throws Exception { + reduceToOne(); + queryAB_Dstar(); + + HashMap tags = new HashMap(2); + tags.put("D", "F"); + tags.put("E", "E"); + tsdb.addPoint("A", 1431561630, 1024, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "Q"); + tags.put("E", "E"); + tsdb.addPoint("B", 1431561630, 1024, tags).joinUninterruptibly(); + + final IntersectionIterator it = new IntersectionIterator("it", iterators, false, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(1, it.getSeriesSize()); + + long ts = 1431561600000L; + double values[] = new double[] { 7, 17 }; + while (it.hasNext()) { + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(1, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(values[0]++, set_dps[0].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(1, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(values[1]++, set_dps[0].toDouble(), 0.0001); + + ts += 60000; + } + } + + @Test + public void threeSeriesIntersectToOne() throws Exception { + reduceToOne(); + queryAB_Dstar(); + + final IntersectionIterator it = new IntersectionIterator("it", iterators, false, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(1, it.getSeriesSize()); + + long ts = 1431561600000L; + double values[] = new double[] { 7, 17 }; + while (it.hasNext()) { + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(1, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(values[0]++, set_dps[0].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(1, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(values[1]++, set_dps[0].toDouble(), 0.0001); + + ts += 60000; + } + } + + @Test + public void threeSeriesAggedIntoOne() throws Exception { + threeSameE(); + queryAB_AggAll(); + + final IntersectionIterator it = new IntersectionIterator("it", iterators, false, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(1, it.getSeriesSize()); + + long ts = 1431561600000L; + double values[] = new double[] { 12, 42 }; + while (it.hasNext()) { + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(1, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(values[0], set_dps[0].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(1, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(values[1], set_dps[0].toDouble(), 0.0001); + + values[0] += 3; + values[1] += 3; + + ts += 60000; + } + } + + @Test + public void threeSeriesFullIntersetWithNaNs() throws Exception { + threeSameEGaps(); + queryAB_Dstar(); + for (ITimeSyncedIterator iterator : iterators.values()) { + iterator.setFillPolicy(new NumericFillPolicy(FillPolicy.NOT_A_NUMBER)); + } + + final IntersectionIterator it = new IntersectionIterator("it", iterators, false, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(3, it.getSeriesSize()); + + long ts = 1431561600000L; + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(3, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertEquals(1, set_dps[0].toDouble(), 0.0001); + assertEquals(4, set_dps[1].toDouble(), 0.0001); + assertTrue(Double.isNaN(set_dps[2].toDouble())); + + // whole series is NaN'd + set_dps = dps.get("1"); + assertEquals(3, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertTrue(Double.isNaN(set_dps[0].toDouble())); + assertTrue(Double.isNaN(set_dps[1].toDouble())); + assertTrue(Double.isNaN(set_dps[2].toDouble())); + + ts += 60000; + it.next(); + + set_dps = dps.get("0"); + assertEquals(3, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertTrue(Double.isNaN(set_dps[0].toDouble())); + assertEquals(5, set_dps[1].toDouble(), 0.0001); + assertEquals(8, set_dps[2].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(3, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertTrue(Double.isNaN(set_dps[0].toDouble())); + assertEquals(15, set_dps[1].toDouble(), 0.0001); + assertTrue(Double.isNaN(set_dps[2].toDouble())); + + ts += 60000; + it.next(); + + set_dps = dps.get("0"); + assertEquals(3, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertEquals(3, set_dps[0].toDouble(), 0.0001); + assertTrue(Double.isNaN(set_dps[1].toDouble())); + assertEquals(9, set_dps[2].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(3, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertEquals(13, set_dps[0].toDouble(), 0.0001); + assertTrue(Double.isNaN(set_dps[1].toDouble())); + assertEquals(19, set_dps[2].toDouble(), 0.0001); + + assertFalse(it.hasNext()); + } + + @Test + public void twoSeriesTimeOffset() throws Exception { + timeOffset(); + queryAB_Dstar(); + for (ITimeSyncedIterator iterator : iterators.values()) { + iterator.setFillPolicy(new NumericFillPolicy(FillPolicy.NOT_A_NUMBER)); + } + + final IntersectionIterator it = new IntersectionIterator("it", iterators, false, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(2, it.getSeriesSize()); + + long ts = 1431561600000L; + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(2, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(1, set_dps[0].toDouble(), 0.0001); + assertEquals(4, set_dps[1].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(2, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertTrue(Double.isNaN(set_dps[0].toDouble())); + assertTrue(Double.isNaN(set_dps[1].toDouble())); + + ts += 60000; + it.next(); + + set_dps = dps.get("0"); + assertEquals(2, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(2, set_dps[0].toDouble(), 0.0001); + assertEquals(5, set_dps[1].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(2, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertTrue(Double.isNaN(set_dps[0].toDouble())); + assertTrue(Double.isNaN(set_dps[1].toDouble())); + + ts += 60000; + it.next(); + + set_dps = dps.get("0"); + assertEquals(2, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertTrue(Double.isNaN(set_dps[0].toDouble())); + assertTrue(Double.isNaN(set_dps[1].toDouble())); + + set_dps = dps.get("1"); + assertEquals(2, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(13, set_dps[0].toDouble(), 0.0001); + assertEquals(16, set_dps[1].toDouble(), 0.0001); + + ts += 60000; + it.next(); + + set_dps = dps.get("0"); + assertEquals(2, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertTrue(Double.isNaN(set_dps[0].toDouble())); + assertTrue(Double.isNaN(set_dps[1].toDouble())); + + set_dps = dps.get("1"); + assertEquals(2, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(14, set_dps[0].toDouble(), 0.0001); + assertEquals(17, set_dps[1].toDouble(), 0.0001); + + assertFalse(it.hasNext()); + } + + @Test (expected = IllegalDataException.class) + public void noIntersectionUsingResultTags() throws Exception { + threeDifE(); + queryAB_Dstar(); + new IntersectionIterator("it", iterators, false, false); + } + + @Test + public void intersectUsingQueryTags() throws Exception { + threeDifE(); + queryAB_Dstar(); + + final IntersectionIterator it = new IntersectionIterator("it", iterators, true, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(3, it.getSeriesSize()); + + long ts = 1431561600000L; + double values[] = new double[] { 1, 4, 7, 11, 14, 17 }; + while (it.hasNext()) { + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(3, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertEquals(values[0]++, set_dps[0].toDouble(), 0.0001); + assertEquals(values[1]++, set_dps[1].toDouble(), 0.0001); + assertEquals(values[2]++, set_dps[2].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(3, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertEquals(values[3]++, set_dps[0].toDouble(), 0.0001); + assertEquals(values[4]++, set_dps[1].toDouble(), 0.0001); + assertEquals(values[5]++, set_dps[2].toDouble(), 0.0001); + + ts += 60000; + } + } + + @Test + public void commonAggregatedTag() throws Exception { + twoSeriesAggedE(); + queryAB_Dstar(); + + final IntersectionIterator it = new IntersectionIterator("it", iterators, false, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(1, it.getSeriesSize()); + + long ts = 1431561600000L; + double values[] = new double[] { 2, 22 }; + while (it.hasNext()) { + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(1, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(values[0], set_dps[0].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(1, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(values[1], set_dps[0].toDouble(), 0.0001); + + values[0] += 2; + values[1] += 2; + + ts += 60000; + } + } + + @Test + public void extraAggTagIgnored() throws Exception { + twoSeriesAggedEandExtraTagK(); + queryAB_Dstar(); + + final IntersectionIterator it = new IntersectionIterator("it", iterators, false, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(1, it.getSeriesSize()); + + long ts = 1431561600000L; + double values[] = new double[] { 2, 22 }; + while (it.hasNext()) { + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(1, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(values[0], set_dps[0].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(1, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(values[1], set_dps[0].toDouble(), 0.0001); + + values[0] += 2; + values[1] += 2; + + ts += 60000; + } + } + + @Test (expected = IllegalDataException.class) + public void extraAggTagNoIntersection() throws Exception { + twoSeriesAggedEandExtraTagK(); + queryAB_Dstar(); + new IntersectionIterator("it", iterators, false, true); + } + + @Test (expected = IllegalDataException.class) + public void onlyOneResultSet() throws Exception { + threeSameENoB(); + queryAB_Dstar(); + new IntersectionIterator("it", iterators, false, true); + } + + @Test (expected = IllegalDataException.class) + public void oneAggedOneTaggedNoIntersection() throws Exception { + oneAggedTheOtherTagged(); + queryAB_AggAll(); + new IntersectionIterator("it", iterators, false, true); + } + + @Test + public void oneAggedOneTaggedUseQueryTagsWoutQueryTags() throws Exception { + oneAggedTheOtherTagged(); + queryAB_AggAll(); + + final IntersectionIterator it = new IntersectionIterator("it", iterators, true, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(1, it.getSeriesSize()); + + long ts = 1431561600000L; + double values[] = new double[] { 2, 11 }; + while (it.hasNext()) { + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(1, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(values[0], set_dps[0].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(1, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(values[1]++, set_dps[0].toDouble(), 0.0001); + + // the first set is agged + values[0] += 2; + + ts += 60000; + } + } + + @Test + public void singleSeries() throws Exception { + oneExtraSameE(); + queryA_DD(); + + final IntersectionIterator it = new IntersectionIterator("it", iterators, false, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(1, dps.size()); + assertEquals(1, it.getSeriesSize()); + + long ts = 1431561600000L; + double value = 1; + while (it.hasNext()) { + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(1, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(value++, set_dps[0].toDouble(), 0.0001); + + ts += 60000; + } + } + + @Test (expected = IllegalDataException.class) + public void setAMissingE() throws Exception { + threeAMissingE(); + queryAB_Dstar(); + new IntersectionIterator("it", iterators, false, false); + } + + @Test + public void setAMissingEQueryTags() throws Exception { + threeAMissingE(); + queryAB_Dstar(); + + final IntersectionIterator it = new IntersectionIterator("it", iterators, true, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(3, it.getSeriesSize()); + + long ts = 1431561600000L; + double values[] = new double[] { 1, 4, 7, 11, 14, 17 }; + while (it.hasNext()) { + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(3, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertEquals(values[0]++, set_dps[0].toDouble(), 0.0001); + assertEquals(values[1]++, set_dps[1].toDouble(), 0.0001); + assertEquals(values[2]++, set_dps[2].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(3, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertEquals(values[3]++, set_dps[0].toDouble(), 0.0001); + assertEquals(values[4]++, set_dps[1].toDouble(), 0.0001); + assertEquals(values[5]++, set_dps[2].toDouble(), 0.0001); + + ts += 60000; + } + } + + @Test + public void noData() throws Exception { + setDataPointStorage(); + queryAB_Dstar(); + + final IntersectionIterator it = new IntersectionIterator("it", iterators, false, false); + final Map dps = it.getResults(); + assertEquals(0, dps.size()); + assertFalse(it.hasNext()); + assertEquals(0, it.getSeriesSize()); + } + + @Test (expected = IllegalDataException.class) + public void nextException() throws Exception { + threeDisjointSameE(); + queryAB_Dstar(); + + final IntersectionIterator it = new IntersectionIterator("it", iterators, false, false); + it.next(); + it.next(); + it.next(); + it.next(); + } + + @Test + public void flattenTags() throws Exception { + final byte[] flat = IntersectionIterator.flattenTags( + false, false, tags, agg_tags, sub); + assertArrayEquals(MockBase.concatByteArrays(UID1, UID1, UID2, UID2), flat); + } + + @Test + public void flattenTagsWithAgg() throws Exception { + final byte[] flat = IntersectionIterator.flattenTags( + false, true, tags, agg_tags, sub); + assertArrayEquals( + MockBase.concatByteArrays(UID1, UID1, UID2, UID2, UID3), flat); + } + + @Test + public void flattenTagsQueryTags() throws Exception { + final byte[] flat = IntersectionIterator.flattenTags( + true, false, tags, agg_tags, sub); + assertArrayEquals(MockBase.concatByteArrays(UID1, UID1), flat); + } + + @Test + public void flattenTagsQueryTagsWithAgg() throws Exception { + final byte[] flat = IntersectionIterator.flattenTags( + true, true, tags, agg_tags, sub); + assertArrayEquals(MockBase.concatByteArrays(UID1, UID1, UID3), flat); + } + + @Test + public void flattenEmptyTags() throws Exception { + tags.clear(); + final byte[] flat = IntersectionIterator.flattenTags( + false, false, tags, agg_tags, sub); + assertArrayEquals(HBaseClient.EMPTY_ARRAY, flat); + } + + @Test + public void flattenEmptyTagsWithAggEmpty() throws Exception { + agg_tags.clear(); + final byte[] flat = IntersectionIterator.flattenTags( + false, true, tags, agg_tags, sub); + assertArrayEquals(MockBase.concatByteArrays(UID1, UID1, UID2, UID2), flat); + } + + // TODO - how will this play out if we choose a default agg of "none" and the + // user hasn't asked for any filtering? + @Test + public void flattenTagsQueryTagsEmpty() throws Exception { + query_tags.clear(); + final byte[] flat = IntersectionIterator.flattenTags( + true, false, tags, agg_tags, sub); + assertArrayEquals(HBaseClient.EMPTY_ARRAY, flat); + } + + @Test + public void flattenTagsQueryTagsEmptyWithAgg() throws Exception { + query_tags.clear(); + final byte[] flat = IntersectionIterator.flattenTags( + true, true, tags, agg_tags, sub); + assertArrayEquals(UID3, flat); + } + + @Test (expected = NullPointerException.class) + public void flattenTagsNullTags() throws Exception { + IntersectionIterator.flattenTags(false, false, null, agg_tags, sub); + } + + @Test + public void flattenTagsNullAggTagsNotRequested() throws Exception { + final byte[] flat = IntersectionIterator.flattenTags( + false, false, tags, null, sub); + assertArrayEquals(MockBase.concatByteArrays(UID1, UID1, UID2, UID2), flat); + } + + @Test (expected = NullPointerException.class) + public void flattenTagsNullAggTags() throws Exception { + IntersectionIterator.flattenTags(false, true, tags, null, sub); + } + + @Test + public void flattenTagsNullSubNotRequested() throws Exception { + final byte[] flat = IntersectionIterator.flattenTags( + false, false, tags, agg_tags, null); + assertArrayEquals(MockBase.concatByteArrays(UID1, UID1, UID2, UID2), flat); + } + + @Test (expected = NullPointerException.class) + public void flattenTagsNullSub() throws Exception { + IntersectionIterator.flattenTags(true, false, tags, agg_tags, null); + } + +} From 6d20e2bf3bd8bf53ded41fe873441eabf471e720 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Thu, 5 Nov 2015 16:23:23 -0800 Subject: [PATCH 066/233] Add the UnionIterator for computing the union across sets using a fill policy for missing entries in any set. Signed-off-by: Chris Larsen --- Makefile.am | 3 +- src/query/expression/UnionIterator.java | 414 +++++++ .../BaseTimeSyncedIteratorTest.java | 14 +- test/query/expression/TestUnionIterator.java | 1074 +++++++++++++++++ 4 files changed, 1498 insertions(+), 7 deletions(-) create mode 100644 src/query/expression/UnionIterator.java create mode 100644 test/query/expression/TestUnionIterator.java diff --git a/Makefile.am b/Makefile.am index 84f76540bf..2916b67f2a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -90,7 +90,7 @@ tsdb_SRC := \ src/query/expression/PostAggregatedDataPoints.java \ src/query/expression/Scale.java \ src/query/expression/TimeSyncedIterator.java \ - src/query/expression/TimeSyncedIterator.java \ + src/query/expression/UnionIterator.java \ src/query/filter/TagVFilter.java \ src/query/filter/TagVLiteralOrFilter.java \ src/query/filter/TagVNotKeyFilter.java \ @@ -261,6 +261,7 @@ test_SRC := \ test/query/expression/TestPostAggregatedDataPoints.java \ test/query/expression/TestScale.java \ test/query/expression/TestTimeSyncedIterator.java \ + test/query/expression/TestUnionIterator.java \ test/query/filter/TestTagVFilter.java \ test/query/filter/TestTagVLiteralOrFilter.java \ test/query/filter/TestTagVNotKeyFilter.java \ diff --git a/src/query/expression/UnionIterator.java b/src/query/expression/UnionIterator.java new file mode 100644 index 0000000000..0ed4514aa1 --- /dev/null +++ b/src/query/expression/UnionIterator.java @@ -0,0 +1,414 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +import net.opentsdb.core.FillPolicy; +import net.opentsdb.core.IllegalDataException; +import net.opentsdb.core.TSDB; +import net.opentsdb.utils.ByteSet; + +import org.hbase.async.HBaseClient; +import org.hbase.async.Bytes.ByteMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sun.reflect.generics.reflectiveObjects.NotImplementedException; + +/** + * An iterator that computes the union of all series in the result sets. This + * means we match every series with it's corresponding series in the other sets. + * If one or more set lacks the matching series, then a {@code null} is stored + * and when the caller iterates over the results, the need to detect the null + * and substitute a fill value. + * @since 2.3 + */ +public class UnionIterator implements ITimeSyncedIterator, VariableIterator { + private static final Logger LOG = LoggerFactory.getLogger(UnionIterator.class); + + /** The queries compiled and fetched from storage */ + private final Map queries; + + /** A list of the current values for each series post intersection */ + private final Map current_values; + + /** A map of the sub query index to their names for intersection computation */ + private final String[] index_to_names; + + /** Whether or not to intersect on the query tagks instead of the result set + * tagks */ + private final boolean union_on_query_tagks; + + /** Whether or not to include the aggregated tags in the result set */ + private final boolean include_agg_tags; + + /** The start/current timestamp for the iterator in ms */ + private long timestamp; + + /** Post intersection number of time series */ + private int series_size; + + /** The ID of this iterator */ + private final String id; + + /** The index of this iterator in a list of iterators */ + private int index; + + /** The fill policy to use when a series is missing from one of the sets. + * Default is zero. */ + private NumericFillPolicy fill_policy; + + /** A data point used for filling missing time series */ + private ExpressionDataPoint fill_dp; + + /** + * Default ctor + * @param id The variable ID for this iterator + * @param results Upstream iterators + * @param union_on_query_tagks Whether or not to flatten and join on only + * the tags from the query or those returned in the results. + * @param include_agg_tags Whether or not to include the flattened aggregated + * tag keys in the join. + */ + public UnionIterator(final String id, final Map results, + final boolean union_on_query_tagks, final boolean include_agg_tags) { + this.id = id; + this.union_on_query_tagks = union_on_query_tagks; + this.include_agg_tags = include_agg_tags; + timestamp = Long.MAX_VALUE; + queries = new HashMap(results.size()); + current_values = new HashMap(results.size()); + index_to_names = new String[results.size()]; + fill_policy = new NumericFillPolicy(FillPolicy.ZERO); + fill_dp = new ExpressionDataPoint(); + + int i = 0; + for (final Map.Entry entry : results.entrySet()) { + if (LOG.isDebugEnabled()) { + LOG.debug("Adding iterator " + entry.getValue()); + } + queries.put(entry.getKey(), entry.getValue()); + entry.getValue().setIndex(i); + index_to_names[i] = entry.getKey(); + ++i; + } + + computeUnion(); + + // calculate the starting timestamp from the various iterators + for (final ITimeSyncedIterator it : queries.values()) { + final long ts = it.nextTimestamp(); + if (ts < timestamp) { + timestamp = ts; + } + } + + if (LOG.isDebugEnabled()) { + LOG.debug("Computed union: " + this); + } + } + + /** + * Private copy constructor that copies references and sets up new collections + * without copying results. + * @param iterator The iterator to copy from. + */ + private UnionIterator(final UnionIterator iterator) { + id = iterator.id; + union_on_query_tagks = iterator.union_on_query_tagks; + include_agg_tags = iterator.include_agg_tags; + timestamp = Long.MAX_VALUE; + queries = new HashMap(iterator.queries.size()); + current_values = new HashMap(queries.size()); + index_to_names = new String[queries.size()]; + fill_policy = iterator.fill_policy; + + int i = 0; + for (final Map.Entry entry : iterator.queries.entrySet()) { + if (LOG.isDebugEnabled()) { + LOG.debug("Adding iterator " + entry.getValue()); + } + queries.put(entry.getKey(), entry.getValue()); + entry.getValue().setIndex(i); + index_to_names[i] = entry.getKey(); + ++i; + } + + computeUnion(); + + // calculate the starting timestamp from the various iterators + for (final ITimeSyncedIterator it : queries.values()) { + final long ts = it.nextTimestamp(); + if (ts < timestamp) { + timestamp = ts; + } + } + } + + /** + * Computes the union of all sets, matching on tags and optionally the + * aggregated tags across each variable. + */ + private void computeUnion() { + // key = flattened tags, array of queries.size() + final ByteMap ordered_union = + new ByteMap(); + + final Iterator it = queries.values().iterator(); + while (it.hasNext()) { + final ITimeSyncedIterator sub = it.next(); + final ExpressionDataPoint[] dps = sub.values(); + final ByteMap local_tags = new ByteMap(); + + for (int i = 0; i < sub.size(); i++) { + final byte[] key = flattenTags(union_on_query_tagks, include_agg_tags, + dps[i], sub); + local_tags.put(key, i); + ExpressionDataPoint[] udps = ordered_union.get(key); + if (udps == null) { + udps = new ExpressionDataPoint[queries.size()]; + ordered_union.put(key, udps); + } + udps[sub.getIndex()] = dps[i]; + } + } + + if (ordered_union.size() < 1) { + // if no data, just stop here + return; + } + + setCurrentAndMeta(ordered_union); + } + + /** + * Takes the resulting union and builds the {@link #current_values} + * and {@link #meta} maps. + * @param ordered_union The union to build from. + */ + private void setCurrentAndMeta(final ByteMap + ordered_union) { + for (final String id : queries.keySet()) { + current_values.put(id, new ExpressionDataPoint[ordered_union.size()]); + } + + int i = 0; + for (final ExpressionDataPoint[] idps : ordered_union.values()) { + for (int x = 0; x < idps.length; x++) { + final ExpressionDataPoint[] current_dps = + current_values.get(index_to_names[x]); + current_dps[i] = idps[x]; + } + ++i; + } + + // set fills on nulls + for (final ExpressionDataPoint[] idps : current_values.values()) { + for (i = 0; i < idps.length; i++) { + if (idps[i] == null) { + idps[i] = fill_dp; + } + } + } + series_size = ordered_union.size(); + } + + /** + * Creates a key based on the concatenation of the tag pairs then the agg + * tag keys. + * @param use_query_tags Whether or not to include tags returned with the + * results or just use those group by'd in the query + * @param include_agg_tags Whether or not to include the aggregated tags in + * the identifier + * @param dp The current expression data point + * @param sub The sub query iterator + * @return A byte array with the flattened tag keys and values. Note that + * if the tags set is empty, this may return an empty array (but not a null + * array) + */ + static byte[] flattenTags(final boolean use_query_tags, + final boolean include_agg_tags, final ExpressionDataPoint dp, + final ITimeSyncedIterator sub) { + if (dp.tags().isEmpty()) { + return HBaseClient.EMPTY_ARRAY; + } + final int tagk_width = TSDB.tagk_width(); + final int tagv_width = TSDB.tagv_width(); + + final ByteSet query_tagks; + // NOTE: We MAY need the agg tags but I'm not sure yet + final int tag_size; + if (use_query_tags) { + int i = 0; + if (sub.getQueryTagKs() != null && !sub.getQueryTagKs().isEmpty()) { + query_tagks = sub.getQueryTagKs(); + for (final Map.Entry pair : dp.tags().entrySet()) { + if (query_tagks.contains(pair.getKey())) { + i++; + } + } + } else { + query_tagks = new ByteSet(); + } + tag_size = i; + } else { + query_tagks = new ByteSet(); + tag_size = dp.tags().size(); + } + + final int length = (tag_size * (tagk_width + tagv_width)) + + (include_agg_tags ? (dp.aggregatedTags().size() * tagk_width) : 0); + final byte[] key = new byte[length]; + int idx = 0; + for (final Entry pair : dp.tags().entrySet()) { + if (use_query_tags && !query_tagks.contains(pair.getKey())) { + continue; + } + System.arraycopy(pair.getKey(), 0, key, idx, tagk_width); + idx += tagk_width; + System.arraycopy(pair.getValue(), 0, key, idx, tagv_width); + idx += tagv_width; + } + if (include_agg_tags) { + for (final byte[] tagk : dp.aggregatedTags()) { + System.arraycopy(tagk, 0, key, idx, tagk_width); + idx += tagk_width; + } + } + return key; + } + + @Override + public String toString() { + final StringBuilder buf = new StringBuilder(); + buf.append("UnionIterator(id=") + .append(id) + .append(", useQueryTags=") + .append(union_on_query_tagks) + .append(", includeAggTags=") + .append(include_agg_tags) + .append(", index=") + .append(index) + .append(", queries=") + .append(queries); + return buf.toString(); + } + + // Iterator implementations + + @Override + public boolean hasNext() { + for (final ITimeSyncedIterator sub : queries.values()) { + if (sub.hasNext()) { + return true; + } + } + return false; + } + + @Override + public ExpressionDataPoint[] next(long timestamp) { + throw new NotImplementedException(); + } + + @Override + public long nextTimestamp() { + long ts = Long.MAX_VALUE; + for (final ITimeSyncedIterator sub : queries.values()) { + if (sub != null) { + final long t = sub.nextTimestamp(); + if (t < ts) { + ts = t; + } + } + } + return ts; + } + + @Override + public int size() { + throw new NotImplementedException(); + } + + @Override + public ExpressionDataPoint[] values() { + throw new NotImplementedException(); + } + + @Override + public void nullIterator(int index) { + throw new NotImplementedException(); + } + + @Override + public int getIndex() { + return index; + } + + @Override + public void setIndex(int index) { + this.index = index; + } + + @Override + public String getId() { + return id; + } + + @Override + public ByteSet getQueryTagKs() { + throw new NotImplementedException(); + } + + @Override + public void setFillPolicy(NumericFillPolicy policy) { + this.fill_policy = policy; + } + + @Override + public NumericFillPolicy getFillPolicy() { + return fill_policy; + } + + @Override + public ITimeSyncedIterator getCopy() { + return new UnionIterator(this); + } + + @Override + public void next() { + if (!hasNext()) { + throw new IllegalDataException("No more data"); + } + for (final ITimeSyncedIterator sub : queries.values()) { + sub.next(timestamp); + } + // reset the fill data point + fill_dp.reset(timestamp, fill_policy.getValue()); + timestamp = nextTimestamp(); + } + + @Override + public Map getResults() { + return current_values; + } + + @Override + public int getSeriesSize() { + return series_size; + } +} diff --git a/test/query/expression/BaseTimeSyncedIteratorTest.java b/test/query/expression/BaseTimeSyncedIteratorTest.java index 4d8b0a0cb8..252fbb8796 100644 --- a/test/query/expression/BaseTimeSyncedIteratorTest.java +++ b/test/query/expression/BaseTimeSyncedIteratorTest.java @@ -18,6 +18,7 @@ import net.opentsdb.core.BaseTsdbTest; import net.opentsdb.core.DataPoints; +import net.opentsdb.core.FillPolicy; import net.opentsdb.core.Query; import net.opentsdb.core.TSQuery; import net.opentsdb.core.TSSubQuery; @@ -132,9 +133,10 @@ protected void runQueries(final ArrayList subs) throws Exception { results.put(Integer.toString(index), new Pair( query.getQueries().get(index), dps)); - iterators.put(Integer.toString(index), - new TimeSyncedIterator(Integer.toString(index), - query.getQueries().get(index).getFilterTagKs(), dps)); + final ITimeSyncedIterator it = new TimeSyncedIterator(Integer.toString(index), + query.getQueries().get(index).getFilterTagKs(), dps); + it.setFillPolicy(new NumericFillPolicy(FillPolicy.NOT_A_NUMBER)); + iterators.put(Integer.toString(index), it); index++; } } @@ -311,9 +313,9 @@ protected void oneExtraSameE() throws Exception { tags = new HashMap(2); tags.put("D", "G"); tags.put("E", "E"); - tsdb.addPoint("B", 1431561600, 14, tags).joinUninterruptibly(); - tsdb.addPoint("B", 1431561660, 15, tags).joinUninterruptibly(); - tsdb.addPoint("B", 1431561720, 16, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561600, 17, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561660, 18, tags).joinUninterruptibly(); + tsdb.addPoint("B", 1431561720, 19, tags).joinUninterruptibly(); } /** diff --git a/test/query/expression/TestUnionIterator.java b/test/query/expression/TestUnionIterator.java new file mode 100644 index 0000000000..24083eefc5 --- /dev/null +++ b/test/query/expression/TestUnionIterator.java @@ -0,0 +1,1074 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.HashMap; +import java.util.Map; + +import org.hbase.async.HBaseClient; +import org.hbase.async.Bytes.ByteMap; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import net.opentsdb.core.DataPoint; +import net.opentsdb.core.FillPolicy; +import net.opentsdb.core.IllegalDataException; +import net.opentsdb.storage.MockBase; +import net.opentsdb.utils.ByteSet; + +@RunWith(PowerMockRunner.class) +@PowerMockIgnore({"javax.management.*", "javax.xml.*", + "ch.qos.*", "org.slf4j.*", + "com.sum.*", "org.xml.*"}) +@PrepareForTest({ TimeSyncedIterator.class }) +public class TestUnionIterator extends BaseTimeSyncedIteratorTest { + + /** used for the flattenTags tests */ + private static final byte[] UID1 = new byte[] { 0, 0, 1 }; + private static final byte[] UID2 = new byte[] { 0, 0, 2 }; + private static final byte[] UID3 = new byte[] { 0, 0, 3 }; + private ByteMap tags; + private ByteSet agg_tags; + private ITimeSyncedIterator sub; + private ByteSet query_tags; + private NumericFillPolicy fill_policy; + + @Before + public void beforeLocal() throws Exception { + tags = new ByteMap(); + tags.put(UID1, UID1); + tags.put(UID2, UID2); + + agg_tags = new ByteSet(); + agg_tags.add(UID3); + + fill_policy = new NumericFillPolicy(FillPolicy.NOT_A_NUMBER); + sub = mock(ITimeSyncedIterator.class); + query_tags = new ByteSet(); + query_tags.add(UID1); + when(sub.getQueryTagKs()).thenReturn(query_tags); + when(sub.getFillPolicy()).thenReturn(fill_policy); + } + + @Test (expected = NullPointerException.class) + public void ctorNullResults() { + new UnionIterator("it", null, true, true); + } + + @Test + public void ctorEmptyResults() { + final UnionIterator it = new UnionIterator("it", + new HashMap(), true, true); + assertEquals(0, it.getSeriesSize()); + assertFalse(it.hasNext()); + } + + @Test + public void twoAndThreeSeries() throws Exception { + oneExtraSameE(); + queryAB_Dstar(); + + final UnionIterator it = new UnionIterator("it", iterators, false, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(3, it.getSeriesSize()); + + long ts = 1431561600000L; + double values[] = new double[] { 1, 4, 11, 14, 17 }; + while (it.hasNext()) { + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(3, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertEquals(values[0]++, set_dps[0].toDouble(), 0.0001); + assertEquals(values[1]++, set_dps[1].toDouble(), 0.0001); + assertEquals(0, set_dps[2].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(3, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertEquals(values[2]++, set_dps[0].toDouble(), 0.0001); + assertEquals(values[3]++, set_dps[1].toDouble(), 0.0001); + assertEquals(values[4]++, set_dps[2].toDouble(), 0.0001); + ts += 60000; + } + } + + @Test + public void twoAndThreeSeriesExtraDP() throws Exception { + oneExtraSameE(); + queryAB_Dstar(); + + final HashMap tags = new HashMap(2); + tags.put("D", "G"); + tags.put("E", "E"); + tsdb.addPoint("B", 1431561630, 14, tags).joinUninterruptibly(); + + final UnionIterator it = new UnionIterator("it", iterators, false, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(3, it.getSeriesSize()); + + long ts = 1431561600000L; + double values[] = new double[] { 1, 4, 11, 14, 17 }; + while (it.hasNext()) { + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(3, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertEquals(values[0]++, set_dps[0].toDouble(), 0.0001); + assertEquals(values[1]++, set_dps[1].toDouble(), 0.0001); + assertEquals(0, set_dps[2].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertEquals(values[2]++, set_dps[0].toDouble(), 0.0001); + assertEquals(values[3]++, set_dps[1].toDouble(), 0.0001); + assertEquals(values[4]++, set_dps[2].toDouble(), 0.0001); + + ts += 60000; + } + } + + @Test + public void threeSeriesUnionToFour() throws Exception { + threeDisjointSameE(); + queryAB_Dstar(); + + final UnionIterator it = new UnionIterator("it", iterators, false, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(4, it.getSeriesSize()); + + long ts = 1431561600000L; + double values[] = new double[] { 1, 4, 7, 11, 17, 14 }; + while (it.hasNext()) { + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(4, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertEquals(ts, set_dps[3].timestamp()); + assertEquals(values[0]++, set_dps[0].toDouble(), 0.0001); + assertEquals(values[1]++, set_dps[1].toDouble(), 0.0001); + assertEquals(values[2]++, set_dps[2].toDouble(), 0.0001); + assertEquals(0, set_dps[3].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(4, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertEquals(ts, set_dps[3].timestamp()); + assertEquals(values[3]++, set_dps[0].toDouble(), 0.0001); + assertEquals(0, set_dps[1].toDouble(), 0.0001); + assertEquals(values[4]++, set_dps[2].toDouble(), 0.0001); + assertEquals(values[5]++, set_dps[3].toDouble(), 0.0001); + + ts += 60000; + } + } + + @Test + public void threeSeriesUnionToExtraDPs() throws Exception { + reduceToOne(); // though we won't :) + queryAB_Dstar(); + + HashMap tags = new HashMap(2); + tags.put("D", "F"); + tags.put("E", "E"); + tsdb.addPoint("A", 1431561630, 1024, tags).joinUninterruptibly(); + + tags = new HashMap(2); + tags.put("D", "Q"); + tags.put("E", "E"); + tsdb.addPoint("B", 1431561630, 1024, tags).joinUninterruptibly(); + + final UnionIterator it = new UnionIterator("it", iterators, false, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(5, it.getSeriesSize()); + + long ts = 1431561600000L; + double values[] = new double[] { 1, 4, 7, 17, 11, 14 }; + while (it.hasNext()) { + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(5, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertEquals(ts, set_dps[3].timestamp()); + assertEquals(ts, set_dps[4].timestamp()); + assertEquals(values[0]++, set_dps[0].toDouble(), 0.0001); + assertEquals(values[1]++, set_dps[1].toDouble(), 0.0001); + assertEquals(values[2]++, set_dps[2].toDouble(), 0.0001); + assertEquals(0, set_dps[3].toDouble(), 0.0001); + assertEquals(0, set_dps[4].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(5, set_dps.length); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertEquals(ts, set_dps[3].timestamp()); + assertEquals(ts, set_dps[4].timestamp()); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(0, set_dps[0].toDouble(), 0.0001); + assertEquals(0, set_dps[1].toDouble(), 0.0001); + assertEquals(values[3]++, set_dps[2].toDouble(), 0.0001); + assertEquals(values[4]++, set_dps[3].toDouble(), 0.0001); + assertEquals(values[5]++, set_dps[4].toDouble(), 0.0001); + + ts += 60000; + } + } + + @Test + public void threeSeriesAgged() throws Exception { + threeSameE(); + queryAB_AggAll(); + + final UnionIterator it = new UnionIterator("it", iterators, false, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(1, it.getSeriesSize()); + + long ts = 1431561600000L; + double values[] = new double[] { 12, 42 }; + while (it.hasNext()) { + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(1, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(values[0], set_dps[0].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(1, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(values[1], set_dps[0].toDouble(), 0.0001); + + values[0] += 3; + values[1] += 3; + + ts += 60000; + } + } + + @Test + public void threeSeriesWithNaNs() throws Exception { + threeSameEGaps(); + queryAB_Dstar(); + for (ITimeSyncedIterator iterator : iterators.values()) { + iterator.setFillPolicy(new NumericFillPolicy(FillPolicy.NOT_A_NUMBER)); + } + + final UnionIterator it = new UnionIterator("it", iterators, false, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(3, it.getSeriesSize()); + + long ts = 1431561600000L; + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(3, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertEquals(1, set_dps[0].toDouble(), 0.0001); + assertEquals(4, set_dps[1].toDouble(), 0.0001); + assertTrue(Double.isNaN(set_dps[2].toDouble())); + + // whole series is NaN'd + set_dps = dps.get("1"); + assertEquals(3, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertTrue(Double.isNaN(set_dps[0].toDouble())); + assertTrue(Double.isNaN(set_dps[1].toDouble())); + assertTrue(Double.isNaN(set_dps[2].toDouble())); + + ts += 60000; + it.next(); + + set_dps = dps.get("0"); + assertEquals(3, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertTrue(Double.isNaN(set_dps[0].toDouble())); + assertEquals(5, set_dps[1].toDouble(), 0.0001); + assertEquals(8, set_dps[2].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(3, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertTrue(Double.isNaN(set_dps[0].toDouble())); + assertEquals(15, set_dps[1].toDouble(), 0.0001); + assertTrue(Double.isNaN(set_dps[2].toDouble())); + + ts += 60000; + it.next(); + + set_dps = dps.get("0"); + assertEquals(3, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertEquals(3, set_dps[0].toDouble(), 0.0001); + assertTrue(Double.isNaN(set_dps[1].toDouble())); + assertEquals(9, set_dps[2].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(3, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertEquals(13, set_dps[0].toDouble(), 0.0001); + assertTrue(Double.isNaN(set_dps[1].toDouble())); + assertEquals(19, set_dps[2].toDouble(), 0.0001); + + assertFalse(it.hasNext()); + } + + @Test + public void twoSeriesTimeOffset() throws Exception { + timeOffset(); + queryAB_Dstar(); + for (ITimeSyncedIterator iterator : iterators.values()) { + iterator.setFillPolicy(new NumericFillPolicy(FillPolicy.NOT_A_NUMBER)); + } + + final UnionIterator it = new UnionIterator("it", iterators, false, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(2, it.getSeriesSize()); + + long ts = 1431561600000L; + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(2, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(1, set_dps[0].toDouble(), 0.0001); + assertEquals(4, set_dps[1].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(2, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertTrue(Double.isNaN(set_dps[0].toDouble())); + assertTrue(Double.isNaN(set_dps[1].toDouble())); + + ts += 60000; + it.next(); + + set_dps = dps.get("0"); + assertEquals(2, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(2, set_dps[0].toDouble(), 0.0001); + assertEquals(5, set_dps[1].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(2, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertTrue(Double.isNaN(set_dps[0].toDouble())); + assertTrue(Double.isNaN(set_dps[1].toDouble())); + + ts += 60000; + it.next(); + + set_dps = dps.get("0"); + assertEquals(2, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertTrue(Double.isNaN(set_dps[0].toDouble())); + assertTrue(Double.isNaN(set_dps[1].toDouble())); + + set_dps = dps.get("1"); + assertEquals(2, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(13, set_dps[0].toDouble(), 0.0001); + assertEquals(16, set_dps[1].toDouble(), 0.0001); + + ts += 60000; + it.next(); + + set_dps = dps.get("0"); + assertEquals(2, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertTrue(Double.isNaN(set_dps[0].toDouble())); + assertTrue(Double.isNaN(set_dps[1].toDouble())); + + set_dps = dps.get("1"); + assertEquals(2, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(14, set_dps[0].toDouble(), 0.0001); + assertEquals(17, set_dps[1].toDouble(), 0.0001); + + assertFalse(it.hasNext()); + } + + @Test + public void threeSeriesUsingResultTags() throws Exception { + threeDifE(); + queryAB_Dstar(); + final UnionIterator it = new UnionIterator("it", iterators, false, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(6, it.getSeriesSize()); + + long ts = 1431561600000L; + double values[] = new double[] { 1, 4, 7, 11, 14, 17 }; + while (it.hasNext()) { + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(6, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertEquals(ts, set_dps[3].timestamp()); + assertEquals(ts, set_dps[4].timestamp()); + assertEquals(ts, set_dps[5].timestamp()); + assertEquals(values[0]++, set_dps[0].toDouble(), 0.0001); + assertEquals(0, set_dps[1].toDouble(), 0.0001); + assertEquals(values[1]++, set_dps[2].toDouble(), 0.0001); + assertEquals(0, set_dps[3].toDouble(), 0.0001); + assertEquals(values[2]++, set_dps[4].toDouble(), 0.0001); + assertEquals(0, set_dps[5].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(6, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertEquals(ts, set_dps[3].timestamp()); + assertEquals(ts, set_dps[4].timestamp()); + assertEquals(ts, set_dps[5].timestamp()); + assertEquals(0, set_dps[0].toDouble(), 0.0001); + assertEquals(values[3]++, set_dps[1].toDouble(), 0.0001); + assertEquals(0, set_dps[2].toDouble(), 0.0001); + assertEquals(values[4]++, set_dps[3].toDouble(), 0.0001); + assertEquals(0, set_dps[4].toDouble(), 0.0001); + assertEquals(values[5]++, set_dps[5].toDouble(), 0.0001); + + ts += 60000; + } + } + + @Test + public void threeSeriesUsingQueryTags() throws Exception { + threeDifE(); + queryAB_Dstar(); + final UnionIterator it = new UnionIterator("it", iterators, true, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(3, it.getSeriesSize()); + + long ts = 1431561600000L; + double values[] = new double[] { 1, 4, 7, 11, 14, 17 }; + while (it.hasNext()) { + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(3, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertEquals(values[0]++, set_dps[0].toDouble(), 0.0001); + assertEquals(values[1]++, set_dps[1].toDouble(), 0.0001); + assertEquals(values[2]++, set_dps[2].toDouble(), 0.0001); + //assertEquals(0, set_dps[3].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(3, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertEquals(values[3]++, set_dps[0].toDouble(), 0.0001); + assertEquals(values[4]++, set_dps[1].toDouble(), 0.0001); + assertEquals(values[5]++, set_dps[2].toDouble(), 0.0001); + + ts += 60000; + } + } + + @Test + public void commonAggregatedTag() throws Exception { + twoSeriesAggedE(); + queryAB_Dstar(); + + final UnionIterator it = new UnionIterator("it", iterators, false, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(1, it.getSeriesSize()); + + long ts = 1431561600000L; + double values[] = new double[] { 2, 22 }; + while (it.hasNext()) { + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(1, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(values[0], set_dps[0].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(1, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(values[1], set_dps[0].toDouble(), 0.0001); + + values[0] += 2; + values[1] += 2; + + ts += 60000; + } + } + + @Test + public void extraAggTagIgnored() throws Exception { + twoSeriesAggedEandExtraTagK(); + queryAB_Dstar(); + + final UnionIterator it = new UnionIterator("it", iterators, false, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(1, it.getSeriesSize()); + + long ts = 1431561600000L; + double values[] = new double[] { 2, 22 }; + while (it.hasNext()) { + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(1, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(values[0], set_dps[0].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(1, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(values[1], set_dps[0].toDouble(), 0.0001); + + values[0] += 2; + values[1] += 2; + + ts += 60000; + } + } + + @Test + public void extraAggTag() throws Exception { + twoSeriesAggedEandExtraTagK(); + queryAB_Dstar(); + + final UnionIterator it = new UnionIterator("it", iterators, false, true); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(2, it.getSeriesSize()); + + long ts = 1431561600000L; + double values[] = new double[] { 2, 22 }; + while (it.hasNext()) { + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(2, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(values[0], set_dps[0].toDouble(), 0.0001); + assertEquals(0, set_dps[1].toDouble(), 0.0001); + values[0] += 2; + + set_dps = dps.get("1"); + assertEquals(2, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(0, set_dps[0].toDouble(), 0.0001); + assertEquals(values[1], set_dps[1].toDouble(), 0.0001); + values[1] += 2; + + ts += 60000; + } + } + + @Test + public void onlyOneResultSet() throws Exception { + threeSameENoB(); + queryAB_Dstar(); + + final UnionIterator it = new UnionIterator("it", iterators, false, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(3, it.getSeriesSize()); + + long ts = 1431561600000L; + double values[] = new double[] { 1, 4, 7 }; + while (it.hasNext()) { + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(3, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertEquals(values[0]++, set_dps[0].toDouble(), 0.0001); + assertEquals(values[1]++, set_dps[1].toDouble(), 0.0001); + assertEquals(values[2]++, set_dps[2].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(3, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertEquals(0, set_dps[0].toDouble(), 0.0001); + assertEquals(0, set_dps[1].toDouble(), 0.0001); + assertEquals(0, set_dps[2].toDouble(), 0.0001); + + ts += 60000; + } + } + + @Test + public void onlyOneResultSetQueryTags() throws Exception { + threeSameENoB(); + queryAB_Dstar(); + + final UnionIterator it = new UnionIterator("it", iterators, true, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(3, it.getSeriesSize()); + + long ts = 1431561600000L; + double values[] = new double[] { 1, 4, 7 }; + while (it.hasNext()) { + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(3, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertEquals(values[0]++, set_dps[0].toDouble(), 0.0001); + assertEquals(values[1]++, set_dps[1].toDouble(), 0.0001); + assertEquals(values[2]++, set_dps[2].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(3, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertEquals(0, set_dps[0].toDouble(), 0.0001); + assertEquals(0, set_dps[1].toDouble(), 0.0001); + assertEquals(0, set_dps[2].toDouble(), 0.0001); + + ts += 60000; + } + } + + @Test + public void onlyOneResultSetAggTags() throws Exception { + threeSameENoB(); + queryAB_Dstar(); + + final UnionIterator it = new UnionIterator("it", iterators, false, true); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(3, it.getSeriesSize()); + + long ts = 1431561600000L; + double values[] = new double[] { 1, 4, 7 }; + while (it.hasNext()) { + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(3, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertEquals(values[0]++, set_dps[0].toDouble(), 0.0001); + assertEquals(values[1]++, set_dps[1].toDouble(), 0.0001); + assertEquals(values[2]++, set_dps[2].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(3, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertEquals(0, set_dps[0].toDouble(), 0.0001); + assertEquals(0, set_dps[1].toDouble(), 0.0001); + assertEquals(0, set_dps[2].toDouble(), 0.0001); + + ts += 60000; + } + } + + @Test + public void oneAggedOneTagged() throws Exception { + oneAggedTheOtherTagged(); + queryAB_AggAll(); + final UnionIterator it = new UnionIterator("it", iterators, false, true); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(2, it.getSeriesSize()); + + long ts = 1431561600000L; + double values[] = new double[] { 2, 11 }; + while (it.hasNext()) { + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(2, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(values[0], set_dps[0].toDouble(), 0.0001); + assertEquals(0, set_dps[1].toDouble(), 0.0001); + values[0] += 2; + + set_dps = dps.get("1"); + assertEquals(2, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + System.out.println(set_dps[0].toDouble()); + System.out.println(set_dps[1].toDouble()); + assertEquals(0, set_dps[0].toDouble(), 0.0001); + assertEquals(values[1]++, set_dps[1].toDouble(), 0.0001); + + ts += 60000; + } + } + + @Test + public void oneAggedOneTaggedUseQueryTagsWoutQueryTags() throws Exception { + oneAggedTheOtherTagged(); + queryAB_AggAll(); + + final UnionIterator it = new UnionIterator("it", iterators, true, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(1, it.getSeriesSize()); + + long ts = 1431561600000L; + double values[] = new double[] { 2, 11 }; + while (it.hasNext()) { + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(1, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(values[0], set_dps[0].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(1, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(values[1]++, set_dps[0].toDouble(), 0.0001); + + // the first set is agged + values[0] += 2; + + ts += 60000; + } + } + + @Test + public void singleSeries() throws Exception { + oneExtraSameE(); + queryA_DD(); + + final UnionIterator it = new UnionIterator("it", iterators, false, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(1, dps.size()); + assertEquals(1, it.getSeriesSize()); + + long ts = 1431561600000L; + double value = 1; + while (it.hasNext()) { + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(1, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(value++, set_dps[0].toDouble(), 0.0001); + + ts += 60000; + } + } + + @Test + public void setAMissingE() throws Exception { + threeAMissingE(); + queryAB_Dstar(); + + final UnionIterator it = new UnionIterator("it", iterators, false, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(6, it.getSeriesSize()); + + long ts = 1431561600000L; + double values[] = new double[] { 1, 4, 7, 11, 14, 17 }; + while (it.hasNext()) { + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(6, set_dps.length); + for (int i = 0; i < set_dps.length; i++) { + assertEquals(ts, set_dps[i].timestamp()); + } + assertEquals(values[0]++, set_dps[0].toDouble(), 0.0001); + assertEquals(0, set_dps[1].toDouble(), 0.0001); + assertEquals(values[1]++, set_dps[2].toDouble(), 0.0001); + assertEquals(0, set_dps[3].toDouble(), 0.0001); + assertEquals(values[2]++, set_dps[4].toDouble(), 0.0001); + assertEquals(0, set_dps[5].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(6, set_dps.length); + for (int i = 0; i < set_dps.length; i++) { + assertEquals(ts, set_dps[i].timestamp()); + } + assertEquals(0, set_dps[0].toDouble(), 0.0001); + assertEquals(values[3]++, set_dps[1].toDouble(), 0.0001); + assertEquals(0, set_dps[2].toDouble(), 0.0001); + assertEquals(values[4]++, set_dps[3].toDouble(), 0.0001); + assertEquals(0, set_dps[4].toDouble(), 0.0001); + assertEquals(values[5]++, set_dps[5].toDouble(), 0.0001); + + ts += 60000; + } + } + + @Test + public void setAMissingEQueryTags() throws Exception { + threeAMissingE(); + queryAB_Dstar(); + + final UnionIterator it = new UnionIterator("it", iterators, true, false); + final Map dps = it.getResults(); + assertTrue(it.hasNext()); + assertEquals(2, dps.size()); + assertEquals(3, it.getSeriesSize()); + + long ts = 1431561600000L; + double values[] = new double[] { 1, 4, 7, 11, 14, 17 }; + while (it.hasNext()) { + it.next(); + + DataPoint[] set_dps = dps.get("0"); + assertEquals(3, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertEquals(values[0]++, set_dps[0].toDouble(), 0.0001); + assertEquals(values[1]++, set_dps[1].toDouble(), 0.0001); + assertEquals(values[2]++, set_dps[2].toDouble(), 0.0001); + + set_dps = dps.get("1"); + assertEquals(3, set_dps.length); + assertEquals(ts, set_dps[0].timestamp()); + assertEquals(ts, set_dps[1].timestamp()); + assertEquals(ts, set_dps[2].timestamp()); + assertEquals(values[3]++, set_dps[0].toDouble(), 0.0001); + assertEquals(values[4]++, set_dps[1].toDouble(), 0.0001); + assertEquals(values[5]++, set_dps[2].toDouble(), 0.0001); + + ts += 60000; + } + } + + @Test + public void noData() throws Exception { + setDataPointStorage(); + queryAB_Dstar(); + + final UnionIterator it = new UnionIterator("it", iterators, false, false); + final Map dps = it.getResults(); + assertEquals(0, dps.size()); + assertFalse(it.hasNext()); + assertEquals(0, it.getSeriesSize()); + } + + @Test (expected = IllegalDataException.class) + public void nextException() throws Exception { + threeDisjointSameE(); + queryAB_Dstar(); + + final UnionIterator it = new UnionIterator("it", iterators, false, false); + it.next(); + it.next(); + it.next(); + it.next(); + } + + @Test + public void flattenTags() throws Exception { + final ExpressionDataPoint dp = getMockDB(tags, agg_tags); + final byte[] flat = UnionIterator.flattenTags(false, false, dp, sub); + assertArrayEquals(MockBase.concatByteArrays(UID1, UID1, UID2, UID2), flat); + } + + @Test + public void flattenTagsWithAgg() throws Exception { + final ExpressionDataPoint dp = getMockDB(tags, agg_tags); + final byte[] flat = UnionIterator.flattenTags(false, true, dp, sub); + assertArrayEquals( + MockBase.concatByteArrays(UID1, UID1, UID2, UID2, UID3), flat); + } + + @Test + public void flattenTagsQueryTags() throws Exception { + final ExpressionDataPoint dp = getMockDB(tags, agg_tags); + final byte[] flat = UnionIterator.flattenTags(true, false, dp, sub); + assertArrayEquals(MockBase.concatByteArrays(UID1, UID1), flat); + } + + @Test + public void flattenTagsQueryTagsWithAgg() throws Exception { + final ExpressionDataPoint dp = getMockDB(tags, agg_tags); + final byte[] flat = UnionIterator.flattenTags(true, true, dp, sub); + assertArrayEquals(MockBase.concatByteArrays(UID1, UID1, UID3), flat); + } + + @Test + public void flattenEmptyTags() throws Exception { + tags.clear(); + final ExpressionDataPoint dp = getMockDB(tags, agg_tags); + final byte[] flat = UnionIterator.flattenTags(false, false, dp, sub); + assertArrayEquals(HBaseClient.EMPTY_ARRAY, flat); + } + + @Test + public void flattenEmptyTagsWithAggEmpty() throws Exception { + agg_tags.clear(); + final ExpressionDataPoint dp = getMockDB(tags, agg_tags); + final byte[] flat = UnionIterator.flattenTags(false, true, dp, sub); + assertArrayEquals(MockBase.concatByteArrays(UID1, UID1, UID2, UID2), flat); + } + + // TODO - how will this play out if we choose a default agg of "none" and the + // user hasn't asked for any filtering? + @Test + public void flattenTagsQueryTagsEmpty() throws Exception { + query_tags.clear(); + final ExpressionDataPoint dp = getMockDB(tags, agg_tags); + final byte[] flat = UnionIterator.flattenTags(true, false, dp, sub); + assertArrayEquals(HBaseClient.EMPTY_ARRAY, flat); + } + + @Test + public void flattenTagsQueryTagsEmptyWithAgg() throws Exception { + query_tags.clear(); + final ExpressionDataPoint dp = getMockDB(tags, agg_tags); + final byte[] flat = UnionIterator.flattenTags(true, true, dp, sub); + assertArrayEquals(UID3, flat); + } + + @Test (expected = NullPointerException.class) + public void flattenTagsNullTags() throws Exception { + final ExpressionDataPoint dp = getMockDB(null, agg_tags); + UnionIterator.flattenTags(false, false, dp, sub); + } + + @Test + public void flattenTagsNullAggTagsNotRequested() throws Exception { + final ExpressionDataPoint dp = getMockDB(tags, null); + final byte[] flat = UnionIterator.flattenTags(false, false, dp, sub); + assertArrayEquals(MockBase.concatByteArrays(UID1, UID1, UID2, UID2), flat); + } + + @Test (expected = NullPointerException.class) + public void flattenTagsNullAggTags() throws Exception { + final ExpressionDataPoint dp = getMockDB(tags, null); + UnionIterator.flattenTags(false, true, dp, sub); + } + + @Test + public void flattenTagsNullSubNotRequested() throws Exception { + final ExpressionDataPoint dp = getMockDB(tags, agg_tags); + final byte[] flat = UnionIterator.flattenTags(false, false, dp, null); + assertArrayEquals(MockBase.concatByteArrays(UID1, UID1, UID2, UID2), flat); + } + + @Test (expected = NullPointerException.class) + public void flattenTagsNullSub() throws Exception { + final ExpressionDataPoint dp = getMockDB(tags, agg_tags); + UnionIterator.flattenTags(true, false, dp, null); + } + + /** + * A helper to mock out the calls for flatten tags + * @param tags The tags to return + * @param agg_tags The aggregated tags to return + * @return A mocked data point + */ + private ExpressionDataPoint getMockDB(final ByteMap tags, + final ByteSet agg_tags) { + final ExpressionDataPoint dp = mock(ExpressionDataPoint.class); + when(dp.tags()).thenReturn(tags); + when(dp.aggregatedTags()).thenReturn(agg_tags); + return dp; + } +} From e4e712ddd8e83a85998f22984f8be78b58d3f8b9 Mon Sep 17 00:00:00 2001 From: Yulai Fu Date: Thu, 5 Nov 2015 17:06:48 -0800 Subject: [PATCH 067/233] Add pojo classes using the builder pattern for the new query format. Signed-off-by: Chris Larsen --- Makefile.am | 18 ++ src/query/pojo/Downsampler.java | 146 ++++++++++++++ src/query/pojo/Expression.java | 159 ++++++++++++++++ src/query/pojo/Filter.java | 115 +++++++++++ src/query/pojo/Join.java | 132 +++++++++++++ src/query/pojo/Metric.java | 206 ++++++++++++++++++++ src/query/pojo/Output.java | 117 ++++++++++++ src/query/pojo/Query.java | 274 +++++++++++++++++++++++++++ src/query/pojo/Timespan.java | 208 ++++++++++++++++++++ src/query/pojo/Validatable.java | 59 ++++++ test/query/pojo/TestDownsampler.java | 104 ++++++++++ test/query/pojo/TestExpression.java | 96 ++++++++++ test/query/pojo/TestFilter.java | 97 ++++++++++ test/query/pojo/TestJoin.java | 66 +++++++ test/query/pojo/TestMetric.java | 123 ++++++++++++ test/query/pojo/TestOutput.java | 45 +++++ test/query/pojo/TestQuery.java | 223 ++++++++++++++++++++++ test/query/pojo/TestTimeSpan.java | 137 ++++++++++++++ 18 files changed, 2325 insertions(+) create mode 100644 src/query/pojo/Downsampler.java create mode 100644 src/query/pojo/Expression.java create mode 100644 src/query/pojo/Filter.java create mode 100644 src/query/pojo/Join.java create mode 100644 src/query/pojo/Metric.java create mode 100644 src/query/pojo/Output.java create mode 100644 src/query/pojo/Query.java create mode 100644 src/query/pojo/Timespan.java create mode 100644 src/query/pojo/Validatable.java create mode 100644 test/query/pojo/TestDownsampler.java create mode 100644 test/query/pojo/TestExpression.java create mode 100644 test/query/pojo/TestFilter.java create mode 100644 test/query/pojo/TestJoin.java create mode 100644 test/query/pojo/TestMetric.java create mode 100644 test/query/pojo/TestOutput.java create mode 100644 test/query/pojo/TestQuery.java create mode 100644 test/query/pojo/TestTimeSpan.java diff --git a/Makefile.am b/Makefile.am index 2916b67f2a..d9d7e5b29c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -97,6 +97,15 @@ tsdb_SRC := \ src/query/filter/TagVNotLiteralOrFilter.java \ src/query/filter/TagVRegexFilter.java \ src/query/filter/VariableIterator.java \ + src/query/pojo/Downsampler.java \ + src/query/pojo/Expression.java \ + src/query/pojo/Filter.java \ + src/query/pojo/Join.java \ + src/query/pojo/Metric.java \ + src/query/pojo/Output.java \ + src/query/pojo/Query.java \ + src/query/pojo/Timespan.java \ + src/query/pojo/Validatable.java \ src/search/SearchPlugin.java \ src/search/SearchQuery.java \ src/search/TimeSeriesLookup.java \ @@ -268,6 +277,15 @@ test_SRC := \ test/query/filter/TestTagVNotLiteralOrFilter.java \ test/query/filter/TestTagVRegexFilter.java \ test/query/filter/TestTagVWildcardFilter.java \ + test/query/pojo/TestDownsampler.java \ + test/query/pojo/TestExpression.java \ + test/query/pojo/TestFilter.java \ + test/query/pojo/TestJoin.java \ + test/query/pojo/TestMetric.java \ + test/query/pojo/TestOutput.java \ + test/query/pojo/TestQuery.java \ + test/query/pojo/TestTimespan.java \ + test/query/pojo/TestValidatable.java \ test/search/TestSearchPlugin.java \ test/search/TestSearchQuery.java \ test/search/TestTimeSeriesLookup.java \ diff --git a/src/query/pojo/Downsampler.java b/src/query/pojo/Downsampler.java new file mode 100644 index 0000000000..dfda751ffe --- /dev/null +++ b/src/query/pojo/Downsampler.java @@ -0,0 +1,146 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.pojo; + +import java.util.NoSuchElementException; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.common.base.Objects; + +import net.opentsdb.core.Aggregators; +import net.opentsdb.query.expression.NumericFillPolicy; +import net.opentsdb.utils.DateTime; + +/** + * Pojo builder class used for serdes of the downsampler component of a query + * @since 2.3 + */ +@JsonDeserialize(builder = Downsampler.Builder.class) +public class Downsampler extends Validatable { + /** The relative interval with value and unit, e.g. 60s */ + private String interval; + + /** The aggregator to use for downsampling */ + private String aggregator; + + /** A fill policy for downsampling and working with missing values */ + private NumericFillPolicy fill_policy; + + /** + * Default ctor + * @param builder The builder to pull values from + */ + public Downsampler(Builder builder) { + interval = builder.interval; + aggregator = builder.aggregator; + fill_policy = builder.fillPolicy; + } + + /** @return A new builder for the downsampler */ + public static Builder Builder() { + return new Builder(); + } + + /** Validates the downsampler + * @throws IllegalArgumentException if one or more parameters were invalid + */ + public void validate() { + if (interval == null || interval.isEmpty()) { + throw new IllegalArgumentException("Missing or empty interval"); + } + DateTime.parseDuration(interval); + + if (aggregator == null || aggregator.isEmpty()) { + throw new IllegalArgumentException("Missing or empty aggregator"); + } + try { + Aggregators.get(aggregator.toLowerCase()); + } catch (final NoSuchElementException e) { + throw new IllegalArgumentException("Invalid aggregator"); + } + + if (fill_policy != null) { + fill_policy.validate(); + } + } + + /** @return the interval for the downsampler */ + public String getInterval() { + return interval; + } + + /** @return the name of the aggregator to use */ + public String getAggregator() { + return aggregator; + } + + /** @return the fill policy to use */ + public NumericFillPolicy getFillPolicy() { + return fill_policy; + } + + @Override + public boolean equals(final Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + final Downsampler downsampler = (Downsampler) o; + + return Objects.equal(interval, downsampler.interval) + && Objects.equal(aggregator, downsampler.aggregator) + && Objects.equal(fill_policy, downsampler.fill_policy); + } + + @Override + public int hashCode() { + return Objects.hashCode(interval, aggregator, fill_policy); + } + + /** + * A builder for the downsampler component of a query + */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPOJOBuilder(buildMethodName = "build", withPrefix = "") + public static final class Builder { + @JsonProperty + private String interval; + @JsonProperty + private String aggregator; + @JsonProperty + private NumericFillPolicy fillPolicy; + + public Builder setInterval(String interval) { + this.interval = interval; + return this; + } + + public Builder setAggregator(String aggregator) { + this.aggregator = aggregator; + return this; + } + + public Builder setFillPolicy(NumericFillPolicy fill_policy) { + this.fillPolicy = fill_policy; + return this; + } + + public Downsampler build() { + return new Downsampler(this); + } + } +} diff --git a/src/query/pojo/Expression.java b/src/query/pojo/Expression.java new file mode 100644 index 0000000000..9f5aaebdf5 --- /dev/null +++ b/src/query/pojo/Expression.java @@ -0,0 +1,159 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.pojo; + +import net.opentsdb.query.expression.NumericFillPolicy; +import net.opentsdb.query.expression.VariableIterator.SetOperator; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.common.base.Objects; + +/** + * Pojo builder class used for serdes of the expression component of a query + * @since 2.3 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonDeserialize(builder = Expression.Builder.class) +public class Expression extends Validatable { + /** An id for this expression for use in output selection or nested expressions */ + private String id; + + /** The raw expression as a string */ + private String expr; + + /** The joiner operator */ + private Join join; + + /** The fill policy to use for ? */ + private NumericFillPolicy fill_policy; + + /** + * Default ctor + * @param builder The builder to pull values from + */ + protected Expression(Builder builder) { + id = builder.id; + expr = builder.expr; + join = builder.join; + fill_policy = builder.fillPolicy; + } + + /** @return the id for this expression for use in output selection or + * nested expressions */ + public String getId() { + return id; + } + + /** @return the raw expression as a string */ + public String getExpr() { + return expr; + } + + /** @return he joiner operator */ + public Join getJoin() { + return join; + } + + /** @return the fill policy to use for ? */ + public NumericFillPolicy getFillPolicy() { + return fill_policy; + } + + /** @return A new builder for the expression */ + public static Builder Builder() { + return new Builder(); + } + + /** Validates the expression + * @throws IllegalArgumentException if one or more parameters were invalid + */ + public void validate() { + if (id == null || id.isEmpty()) { + throw new IllegalArgumentException("missing or empty id"); + } + Query.validateId(id); + + if (expr == null || expr.isEmpty()) { + throw new IllegalArgumentException("missing or empty expr"); + } + + // others are optional + if (join == null) { + join = Join.Builder().setOperator(SetOperator.UNION).build(); + } + } + + @Override + public boolean equals(final Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + final Expression expression = (Expression) o; + + return Objects.equal(id, expression.id) + && Objects.equal(expr, expression.expr) + && Objects.equal(join, expression.join) + && Objects.equal(fill_policy, expression.fill_policy); + } + + @Override + public int hashCode() { + return Objects.hashCode(id, expr, join, fill_policy); + } + + /** + * A builder for the downsampler component of a query + */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPOJOBuilder(buildMethodName = "build", withPrefix = "") + public static final class Builder { + @JsonProperty + private String id; + @JsonProperty + private String expr; + @JsonProperty + private Join join; + @JsonProperty + private NumericFillPolicy fillPolicy; + + public Builder setId(String id) { + Query.validateId(id); + this.id = id; + return this; + } + + public Builder setExpression(String expr) { + this.expr = expr; + return this; + } + + public Builder setJoin(Join join) { + this.join = join; + return this; + } + + public Builder setFillPolicy(NumericFillPolicy fill_policy) { + this.fillPolicy = fill_policy; + return this; + } + + public Expression build() { + return new Expression(this); + } + } +} diff --git a/src/query/pojo/Filter.java b/src/query/pojo/Filter.java new file mode 100644 index 0000000000..902422da1f --- /dev/null +++ b/src/query/pojo/Filter.java @@ -0,0 +1,115 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.pojo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.common.base.Objects; + +import net.opentsdb.query.filter.TagVFilter; + +import java.util.List; + +/** + * Pojo builder class used for serdes of a filter component of a query + * @since 2.3 + */ +@JsonDeserialize(builder = Filter.Builder.class) +public class Filter extends Validatable { + /** The id of the filter set to use in a metric query */ + private String id; + + /** The list of filters in the filter set */ + private List tags; + + /** + * Default ctor + * @param builder The builder to pull values from + */ + private Filter(Builder builder) { + this.id = builder.id; + this.tags = builder.tags; + } + + /** @return the id of the filter set to use in a metric query */ + public String getId() { + return id; + } + + /** @return the list of filters in the filter set */ + public List getTags() { + return tags; + } + + /** @return A new builder for the filter */ + public static Builder Builder() { + return new Builder(); + } + + /** Validates the filter set + * @throws IllegalArgumentException if one or more parameters were invalid + */ + public void validate() { + if (id == null || id.isEmpty()) { + throw new IllegalArgumentException("Missing or empty id"); + } + Query.validateId(id); + } + + @Override + public boolean equals(final Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + final Filter filter = (Filter) o; + + return Objects.equal(id, filter.id) + && Objects.equal(tags, filter.tags); + } + + @Override + public int hashCode() { + return Objects.hashCode(id, tags); + } + + /** + * A builder for the downsampler component of a query + */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPOJOBuilder(buildMethodName = "build", withPrefix = "") + public static final class Builder { + @JsonProperty + private String id; + @JsonProperty + private List tags; + + public Builder setId(String id) { + Query.validateId(id); + this.id = id; + return this; + } + + public Builder setTags(List tags) { + this.tags = tags; + return this; + } + + public Filter build() { + return new Filter(this); + } + } +} diff --git a/src/query/pojo/Join.java b/src/query/pojo/Join.java new file mode 100644 index 0000000000..ea5c228fa3 --- /dev/null +++ b/src/query/pojo/Join.java @@ -0,0 +1,132 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.pojo; + +import net.opentsdb.query.expression.VariableIterator.SetOperator; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.common.base.Objects; + +/** + * Pojo builder class used for serdes of the join component of a query + * @since 2.3 + */ +@JsonDeserialize(builder = Join.Builder.class) +public class Join extends Validatable { + /** The set operator to use for joining sets */ + private SetOperator operator; + + /** Whether or not to use the original query tags instead of the resulting + * series tags when joining. */ + private boolean use_query_tags = false; + + /** Whether or not to use the aggregated tags in the results when joining. */ + private boolean include_agg_tags = true; + + /** + * Default ctor + * @param builder The builder to pull values from + */ + public Join(final Builder builder) { + operator = builder.operator; + use_query_tags = builder.useQueryTags; + include_agg_tags = builder.includeAggTags; + } + + /** @return the set operator to use for joining sets */ + public SetOperator getOperator() { + return operator; + } + + /** @return whether or not to use the original query tags instead of the + * resulting series tags when joining. */ + public boolean getUseQueryTags() { + return use_query_tags; + } + + /** @return Whether or not to use the aggregated tags in the results + * when joining. */ + public boolean getIncludeAggTags() { + return include_agg_tags; + } + + /** @return A new builder for the joiner */ + public static Builder Builder() { + return new Builder(); + } + + /** Validates the joiner + * @throws IllegalArgumentException if one or more parameters were invalid + */ + @Override + public void validate() { + if (operator == null) { + throw new IllegalArgumentException("Missing join operator"); + } + } + + @Override + public boolean equals(final Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + final Join join = (Join) o; + + return Objects.equal(operator, join.operator) + && Objects.equal(use_query_tags, join.use_query_tags) + && Objects.equal(include_agg_tags, join.include_agg_tags); + } + + @Override + public int hashCode() { + return Objects.hashCode(operator, use_query_tags, include_agg_tags); + } + + /** + * A builder for the downsampler component of a query + */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPOJOBuilder(buildMethodName = "build", withPrefix = "") + public static final class Builder { + @JsonProperty + private SetOperator operator; + @JsonProperty + private boolean useQueryTags = false; + @JsonProperty + private boolean includeAggTags = true; + + public Builder setOperator(final SetOperator operator) { + this.operator = operator; + return this; + } + + public Builder setUseQueryTags(final boolean use_query_tags) { + this.useQueryTags = use_query_tags; + return this; + } + + public Builder setIncludeAggTags(final boolean include_agg_tags) { + this.includeAggTags = include_agg_tags; + return this; + } + + public Join build() { + return new Join(this); + } + } +} diff --git a/src/query/pojo/Metric.java b/src/query/pojo/Metric.java new file mode 100644 index 0000000000..a5e85e0d5d --- /dev/null +++ b/src/query/pojo/Metric.java @@ -0,0 +1,206 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.pojo; + +import java.util.NoSuchElementException; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.common.base.Objects; + +import net.opentsdb.core.Aggregators; +import net.opentsdb.query.expression.NumericFillPolicy; +import net.opentsdb.utils.DateTime; + +/** + * Pojo builder class used for serdes of a metric component of a query + * @since 2.3 + */ +@JsonDeserialize(builder = Metric.Builder.class) +public class Metric extends Validatable { + /** The name of the metric */ + private String metric; + + /** An ID for the metric */ + private String id; + + /** The ID of a filter set */ + private String filter; + + /** An optional time offset for time over time expressions */ + private String time_offset; + + /** An optional aggregation override for the metric */ + private String aggregator; + + /** A fill policy for dealing with missing values in the metric */ + private NumericFillPolicy fill_policy; + + /** + * Default ctor + * @param builder The builder to pull values from + */ + public Metric(Builder builder) { + metric = builder.metric; + id = builder.id; + filter = builder.filter; + time_offset = builder.timeOffset; + aggregator = builder.aggregator; + fill_policy = builder.fillPolicy; + } + + /** @return the name of the metric */ + public String getMetric() { + return metric; + } + + /** @return an ID for the metric */ + public String getId() { + return id; + } + + /** @return the ID of a filter set */ + public String getFilter() { + return filter; + } + + /** @return an optional time offset for time over time expressions */ + public String getTimeOffset() { + return time_offset; + } + + /** @return an optional aggregation override for the metric */ + public String getAggregator() { + return aggregator; + } + + /** @return a fill policy for dealing with missing values in the metric */ + public NumericFillPolicy getFillPolicy() { + return fill_policy; + } + + /** @return A new builder for the metric */ + public static Builder Builder() { + return new Builder(); + } + + /** Validates the metric + * @throws IllegalArgumentException if one or more parameters were invalid + */ + public void validate() { + if (metric == null || metric.isEmpty()) { + throw new IllegalArgumentException("missing or empty metric"); + } + + if (id == null || id.isEmpty()) { + throw new IllegalArgumentException("missing or empty id"); + } + Query.validateId(id); + + if (time_offset != null) { + DateTime.parseDateTimeString(time_offset, null); + } + + if (aggregator != null && !aggregator.isEmpty()) { + try { + Aggregators.get(aggregator.toLowerCase()); + } catch (final NoSuchElementException e) { + throw new IllegalArgumentException("Invalid aggregator"); + } + } + + if (fill_policy != null) { + fill_policy.validate(); + } + } + + @Override + public boolean equals(final Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + final Metric that = (Metric) o; + + return Objects.equal(that.filter, filter) + && Objects.equal(that.id, id) + && Objects.equal(that.metric, metric) + && Objects.equal(that.time_offset, time_offset) + && Objects.equal(that.aggregator, aggregator) + && Objects.equal(that.fill_policy, fill_policy); + } + + @Override + public int hashCode() { + return Objects.hashCode(metric, id, filter, time_offset, aggregator, + fill_policy); + } + + /** + * A builder for a metric component of a query + */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPOJOBuilder(buildMethodName = "build", withPrefix = "") + public static final class Builder { + @JsonProperty + private String metric; + @JsonProperty + private String id; + @JsonProperty + private String filter; + @JsonProperty + private String timeOffset; + @JsonProperty + private String aggregator; + @JsonProperty + private NumericFillPolicy fillPolicy; + + public Builder setMetric(String metric) { + this.metric = metric; + return this; + } + + public Builder setId(String id) { + Query.validateId(id); + this.id = id; + return this; + } + + public Builder setFilter(String filter) { + this.filter = filter; + return this; + } + + public Builder setTimeOffset(String time_offset) { + this.timeOffset = time_offset; + return this; + } + + public Builder setAggregator(String aggregator) { + this.aggregator = aggregator; + return this; + } + + public Builder setFillPolicy(NumericFillPolicy fill_policy) { + this.fillPolicy = fill_policy; + return this; + } + + public Metric build() { + return new Metric(this); + } + } +} diff --git a/src/query/pojo/Output.java b/src/query/pojo/Output.java new file mode 100644 index 0000000000..8b996cf9ab --- /dev/null +++ b/src/query/pojo/Output.java @@ -0,0 +1,117 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.pojo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.common.base.Objects; + +/** + * Pojo builder class used for serdes of the output component of a query + * @since 2.3 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonDeserialize(builder = Output.Builder.class) +public class Output extends Validatable { + /** The ID of a metric or expression to emit */ + private String id; + + /** An alias to use as the metric name for the output */ + private String alias; + + /** + * Default ctor + * @param builder The builder to pull values from + */ + public Output(Builder builder) { + this.id = builder.id; + this.alias = builder.alias; + } + + /** @return the ID of a metric or expression to emit */ + public String getId() { + return id; + } + + /** @return an alias to use as the metric name for the output */ + public String getAlias() { + return alias; + } + + /** @return A new builder for the output */ + public static Builder Builder() { + return new Builder(); + } + + /** Validates the output + * @throws IllegalArgumentException if one or more parameters were invalid + */ + @Override public void validate() { + if (id == null || id.isEmpty()) { + throw new IllegalArgumentException("missing or empty id"); + } + Query.validateId(id); + } + + @Override + public String toString() { + return "var=" + id + ", alias=" + alias; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + Output output = (Output) o; + + return Objects.equal(output.alias, alias) + && Objects.equal(output.id, id); + } + + @Override + public int hashCode() { + return Objects.hashCode(id, alias); + } + + /** + * A builder for the downsampler component of a query + */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPOJOBuilder(buildMethodName = "build", withPrefix = "") + public static final class Builder { + @JsonProperty + private String id; + @JsonProperty + private String alias; + + public Builder setId(String id) { + Query.validateId(id); + this.id = id; + return this; + } + + public Builder setAlias(String alias) { + this.alias = alias; + return this; + } + + public Output build() { + return new Output(this); + } + } +} diff --git a/src/query/pojo/Query.java b/src/query/pojo/Query.java new file mode 100644 index 0000000000..09f62dcf78 --- /dev/null +++ b/src/query/pojo/Query.java @@ -0,0 +1,274 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.pojo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.common.base.Objects; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Pojo builder class used for serdes of the expression query + * @since 2.3 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonDeserialize(builder = Query.Builder.class) +public class Query extends Validatable { + /** An optional name for the query */ + private String name; + + /** The timespan component of the query */ + private Timespan time; + + /** A list of filters */ + private List filters; + + /** A list of metrics */ + private List metrics; + + /** A list of expressions */ + private List expressions; + + /** A list of outputs */ + private List outputs; + + /** + * Default ctor + * @param builder The builder to pull values from + */ + public Query(Builder builder) { + this.name = builder.name; + this.time = builder.time; + this.filters = builder.filters; + this.metrics = builder.metrics; + this.expressions = builder.expressions; + this.outputs = builder.outputs; + } + + /** @return an optional name for the query */ + public String getName() { + return name; + } + + /** @return the timespan component of the query */ + public Timespan getTime() { + return time; + } + + /** @return a list of filters */ + public List getFilters() { + return filters; + } + + /** @return a list of metrics */ + public List getMetrics() { + return metrics; + } + + /** @return a list of expressions */ + public List getExpressions() { + return expressions; + } + + /** @return a list of outputs */ + public List getOutputs() { + return outputs; + } + + /** @return A new builder for the query */ + public static Builder Builder() { + return new Builder(); + } + + /** Validates the query + * @throws IllegalArgumentException if one or more parameters were invalid + */ + public void validate() { + if (time == null) { + throw new IllegalArgumentException("missing time"); + } + + validatePOJO(time, "time"); + + if (metrics == null || metrics.isEmpty()) { + throw new IllegalArgumentException("missing or empty metrics"); + } + + final Set metric_ids = new HashSet(); + + for (Metric metric : metrics) { + if (metric_ids.contains(metric.getId())) { + throw new IllegalArgumentException("duplicated metric id: " + + metric.getId()); + } + metric_ids.add(metric.getId()); + } + + final Set filter_ids = new HashSet(); + + for (Filter filter : filters) { + if (filter_ids.contains(filter.getId())) { + throw new IllegalArgumentException("duplicated filter id: " + + filter.getId()); + } + filter_ids.add(filter.getId()); + } + + final Set expression_ids = new HashSet(); + + for (Expression expression : expressions) { + if (expression_ids.contains(expression.getId())) { + throw new IllegalArgumentException("duplicated expression id: " + + expression.getId()); + } + expression_ids.add(expression.getId()); + } + + validateCollection(metrics, "metric"); + + if (filters != null) { + validateCollection(filters, "filter"); + } + + if (expressions != null) { + validateCollection(expressions, "expression"); + } + + validateFilters(); + } + + /** Validates the filters, making sure each metric has a filter + * @throws IllegalArgumentException if one or more parameters were invalid + */ + private void validateFilters() { + final Set ids = new HashSet(); + for (Filter filter : filters) { + ids.add(filter.getId()); + } + + for (Metric metric : metrics) { + if (!ids.contains(metric.getFilter())) { + throw new IllegalArgumentException( + String.format("unrecognized filter id %s in metric %s", + metric.getFilter(), metric.getId())); + } + } + } + + /** + * Makes sure the ID has only letters and characters + * @param id The ID to parse + * @throws IllegalArgumentException if the ID is invalid + */ + public static void validateId(final String id) { + if (id == null || id.isEmpty()) { + throw new IllegalArgumentException("The ID cannot be null or empty"); + } + for (int i = 0; i < id.length(); i++) { + final char c = id.charAt(i); + if (!(Character.isLetterOrDigit(c))) { + throw new IllegalArgumentException("Invalid id (\"" + id + + "\"): illegal character: " + c); + } + } + if (id.length() == 1) { + if (Character.isDigit(id.charAt(0))) { + throw new IllegalArgumentException("The ID cannot be an integer"); + } + } + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + Query query = (Query) o; + + return Objects.equal(query.expressions, expressions) + && Objects.equal(query.filters, filters) + && Objects.equal(query.metrics, metrics) + && Objects.equal(query.name, name) + && Objects.equal(query.outputs, outputs) + && Objects.equal(query.time, time); + } + + @Override + public int hashCode() { + return Objects.hashCode(name, time, filters, metrics, expressions, outputs); + } + + /** + * A builder for the query component of a query + */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPOJOBuilder(buildMethodName = "build", withPrefix = "") + public static final class Builder { + @JsonProperty + private String name; + @JsonProperty + private Timespan time; + @JsonProperty + private List filters; + @JsonProperty + private List metrics; + @JsonProperty + private List expressions; + @JsonProperty + private List outputs; + + public Builder() { } + + public Builder setName(final String name) { + this.name = name; + return this; + } + + public Builder setTime(final Timespan time) { + this.time = time; + return this; + } + + public Builder setFilters(final List filters) { + this.filters = filters; + return this; + } + + public Builder setMetrics(final List metrics) { + this.metrics = metrics; + return this; + } + + public Builder setExpressions(final List expressions) { + this.expressions = expressions; + return this; + } + + public Builder setOutputs(final List outputs) { + this.outputs = outputs; + return this; + } + + public Query build() { + return new Query(this); + } + } + +} diff --git a/src/query/pojo/Timespan.java b/src/query/pojo/Timespan.java new file mode 100644 index 0000000000..96e56ba255 --- /dev/null +++ b/src/query/pojo/Timespan.java @@ -0,0 +1,208 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.pojo; + +import java.util.NoSuchElementException; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.common.base.Objects; + +import net.opentsdb.core.Aggregators; +import net.opentsdb.utils.DateTime; + +/** + * Pojo builder class used for serdes of the timespan component of a query + * @since 2.3 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonDeserialize(builder = Timespan.Builder.class) +public class Timespan extends Validatable { + /** User given start date/time, could be relative or absolute */ + private String start; + + /** User given end date/time, could be relative, absolute or empty */ + private String end; + + /** User's timezone used for converting absolute human readable dates */ + private String timezone; + + /** An optional downsampler for all queries */ + private Downsampler downsampler; + + /** The global aggregator to use */ + private String aggregator; + + /** Whether or not to compute a rate */ + private boolean rate; + + /** + * Default ctor + * @param builder The builder to pull values from + */ + public Timespan(Builder builder) { + start = builder.start; + end = builder.end; + timezone = builder.timezone; + downsampler = builder.downsampler; + aggregator = builder.aggregator; + rate = builder.rate; + } + + /** @return user given start date/time, could be relative or absolute */ + public String getStart() { + return start; + } + + /** @return user given end date/time, could be relative, absolute or empty */ + public String getEnd() { + return end; + } + + /** @return user's timezone used for converting absolute human readable dates */ + public String getTimezone() { + return timezone; + } + + /** @return an optional downsampler for all queries */ + public Downsampler getDownsampler() { + return downsampler; + } + + /** @return the global aggregator to use */ + public String getAggregator() { + return aggregator; + } + + /** @return whether or not to compute a rate */ + public boolean isRate() { + return rate; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + Timespan timespan = (Timespan) o; + + return Objects.equal(timespan.downsampler, downsampler) + && Objects.equal(timespan.end, end) + && Objects.equal(timespan.start, start) + && Objects.equal(timespan.timezone, timezone) + && Objects.equal(timespan.aggregator, aggregator) + && Objects.equal(timespan.rate, rate); + } + + @Override + public int hashCode() { + return Objects.hashCode(start, end, timezone, downsampler, aggregator, rate); + } + + /** @return A new builder for the downsampler */ + public static Builder Builder() { + return new Builder(); + } + + /** Validates the timespan + * @throws IllegalArgumentException if one or more parameters were invalid + */ + public void validate() { + if (start == null || start.isEmpty()) { + throw new IllegalArgumentException("missing or empty start"); + } + DateTime.parseDateTimeString(start, timezone); + + if (end != null && !end.isEmpty()) { + DateTime.parseDateTimeString(end, timezone); + } + + if (downsampler != null) { + downsampler.validate(); + } + + if (aggregator == null || aggregator.isEmpty()) { + throw new IllegalArgumentException("Missing or empty aggregator"); + } + + try { + Aggregators.get(aggregator.toLowerCase()); + } catch (final NoSuchElementException e) { + throw new IllegalArgumentException("Invalid aggregator"); + } + } + + /** + * A builder for the downsampler component of a query + */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonPOJOBuilder(buildMethodName = "build", withPrefix = "") + public static final class Builder { + @JsonProperty + private String start; + + @JsonProperty + private String end; + + @JsonProperty + private String timezone; + + @JsonProperty + private Downsampler downsampler; + + @JsonProperty + private String aggregator; + + @JsonProperty + private boolean rate; + + public Builder setStart(final String start) { + this.start = start; + return this; + } + + public Builder setEnd(final String end) { + this.end = end; + return this; + } + + public Builder setTimezone(final String timezone) { + this.timezone = timezone; + return this; + } + + public Builder setDownsampler(final Downsampler downsample) { + this.downsampler = downsample; + return this; + } + + public Builder setAggregator(final String aggregator) { + this.aggregator = aggregator; + return this; + } + + public Builder setRate(final boolean rate) { + this.rate = rate; + return this; + } + + public Timespan build() { + return new Timespan(this); + } + } + +} diff --git a/src/query/pojo/Validatable.java b/src/query/pojo/Validatable.java new file mode 100644 index 0000000000..77b53af650 --- /dev/null +++ b/src/query/pojo/Validatable.java @@ -0,0 +1,59 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.pojo; + +import java.util.Collection; +import java.util.Iterator; + +/** + * An interface for the pojos to implement to make sure all the bits of the + * expression queries are there + * @since 2.3 + */ +public abstract class Validatable { + abstract public void validate(); + + /** + * Iterate through a field that is a collection of POJOs and validate each of + * them. Inherit member POJO's error message. + * @param collection the validatable POJO collection + * @param name name of the field + */ + void validateCollection(final Collection collection, + final String name) { + Iterator iterator = collection.iterator(); + int i = 0; + while (iterator.hasNext()) { + try { + iterator.next().validate(); + } catch (final IllegalArgumentException e) { + throw new IllegalArgumentException("Invalid " + name + + " at index " + i, e); + } + i++; + } + } + + /** + * Validate a single POJO validate + * @param pojo The POJO object to validate + * @param name name of the field + */ + void validatePOJO(final T pojo, final String name) { + try { + pojo.validate(); + } catch (final IllegalArgumentException e) { + throw new IllegalArgumentException("Invalid " + name, e); + } + } +} diff --git a/test/query/pojo/TestDownsampler.java b/test/query/pojo/TestDownsampler.java new file mode 100644 index 0000000000..9613999238 --- /dev/null +++ b/test/query/pojo/TestDownsampler.java @@ -0,0 +1,104 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.pojo; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import net.opentsdb.core.FillPolicy; +import net.opentsdb.query.expression.NumericFillPolicy; +import net.opentsdb.utils.JSON; + +import org.junit.Test; + +public class TestDownsampler { + + @Test(expected = IllegalArgumentException.class) + public void validationErrorWhenIntervalIsNull() throws Exception { + String json = "{\"aggregator\":\"sum\"}"; + Downsampler downsampler = JSON.parseToObject(json, Downsampler.class); + downsampler.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void validationErrorWhenIntervalIsEmpty() throws Exception { + String json = "{\"interval\":\"\",\"aggregator\":\"sum\"}"; + Downsampler downsampler = JSON.parseToObject(json, Downsampler.class); + downsampler.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void validationErrorWhenIntervalIsInvalid() throws Exception { + String json = "{\"interval\":\"45foo\",\"aggregator\":\"sum\"}"; + Downsampler downsampler = JSON.parseToObject(json, Downsampler.class); + downsampler.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void validationErrorWhenAggregatorIsNull() throws Exception { + String json = "{\"interval\":\"1h\"}"; + Downsampler downsampler = JSON.parseToObject(json, Downsampler.class); + downsampler.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void validationErrorWhenAggregatorIsEmpty() throws Exception { + String json = "{\"interval\":\"1h\",\"aggregator\":\"\"}"; + Downsampler downsampler = JSON.parseToObject(json, Downsampler.class); + downsampler.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void validationErrorWhenAggregatorIsInvalid() throws Exception { + String json = "{\"interval\":\"1h\",\"aggregator\":\"no such agg\"}"; + Downsampler downsampler = JSON.parseToObject(json, Downsampler.class); + downsampler.validate(); + } + + @Test + public void deserialize() throws Exception { + String json = "{\"interval\":\"1h\",\"aggregator\":\"zimsum\"}"; + Downsampler downsampler = JSON.parseToObject(json, Downsampler.class); + downsampler.validate(); + Downsampler expected = Downsampler.Builder() + .setInterval("1h").setAggregator("zimsum").build(); + assertEquals(expected, downsampler); + + json = "{\"interval\":\"1h\",\"aggregator\":\"zimsum\"," + + "\"fillPolicy\":{\"policy\":\"nan\"},\"junkfield\":true}"; + downsampler = JSON.parseToObject(json, Downsampler.class); + downsampler.validate(); + expected = Downsampler.Builder() + .setInterval("1h").setAggregator("zimsum") + .setFillPolicy(new NumericFillPolicy(FillPolicy.NOT_A_NUMBER)).build(); + assertEquals(expected, downsampler); + } + + @Test + public void serialize() throws Exception { + Downsampler downsampler = Downsampler.Builder() + .setInterval("1h").setAggregator("zimsum").build(); + String json = JSON.serializeToString(downsampler); + assertTrue(json.contains("\"interval\":\"1h\"")); + assertTrue(json.contains("\"aggregator\":\"zimsum\"")); + assertTrue(json.contains("\"fillPolicy\":null")); + + downsampler = Downsampler.Builder() + .setInterval("15m").setAggregator("max") + .setFillPolicy(new NumericFillPolicy(FillPolicy.NOT_A_NUMBER)).build(); + json = JSON.serializeToString(downsampler); + assertTrue(json.contains("\"interval\":\"15m\"")); + assertTrue(json.contains("\"aggregator\":\"max\"")); + assertTrue(json.contains("\"fillPolicy\":{")); + assertTrue(json.contains("\"policy\":\"nan\"")); + } +} diff --git a/test/query/pojo/TestExpression.java b/test/query/pojo/TestExpression.java new file mode 100644 index 0000000000..6d1f5afd4e --- /dev/null +++ b/test/query/pojo/TestExpression.java @@ -0,0 +1,96 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.pojo; + +import net.opentsdb.query.expression.VariableIterator.SetOperator; +import net.opentsdb.utils.JSON; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class TestExpression { + @Test(expected = IllegalArgumentException.class) + public void validationErrorWhenIdIsNull() throws Exception { + String json = "{\"expr\":\"a + b + c\"}"; + Expression expression = JSON.parseToObject(json, Expression.class); + expression.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void validationErrorWhenIdIsEmpty() throws Exception { + String json = "{\"expr\":\"a + b + c\",\"id\":\"\"}"; + Expression expression = JSON.parseToObject(json, Expression.class); + expression.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void validationErrorWhenIdIsInvalid() throws Exception { + String json = "{\"expr\":\"a + b + c\",\"id\":\"system.busy\"}"; + Expression expression = JSON.parseToObject(json, Expression.class); + expression.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void validationErrorWhenExprIsNull() throws Exception { + String json = "{\"id\":\"1\"}"; + Expression expression = JSON.parseToObject(json, Expression.class); + expression.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void validationErrorWhenExprIsEmpty() throws Exception { + String json = "{\"id\":\"1\",\"expr\":\"\"}"; + Expression expression = JSON.parseToObject(json, Expression.class); + expression.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void validationErrorWhenJoinIsInvalid() throws Exception { + String json = "{\"expr\":\"a + b + c\",\"id\":\"system.busy\"," + + "\"join\":{\"operator\":\"nosuchjoin\"}}"; + Expression expression = JSON.parseToObject(json, Expression.class); + expression.validate(); + } + + @Test + public void deserialize() throws Exception { + String json = "{\"id\":\"e\",\"expr\":\"a + b + c\"}"; + Expression expression = JSON.parseToObject(json, Expression.class); + expression.validate(); + Expression expected = Expression.Builder().setId("e") + .setExpression("a + b + c").setJoin( + Join.Builder().setOperator(SetOperator.UNION).build()).build(); + assertEquals(expected, expression); + } + + @Test + public void serialize() throws Exception { + Expression expression = Expression.Builder().setId("e1") + .setJoin(Join.Builder().setOperator(SetOperator.UNION).build()) + .setExpression("a + b + c").build(); + String actual = JSON.serializeToString(expression); + assertTrue(actual.contains("\"id\":\"e1\"")); + assertTrue(actual.contains("\"expr\":\"a + b + c\"")); + assertTrue(actual.contains("\"join\":{\"operator\":\"union\"")); + + } + + @Test + public void unknownShouldBeIgnored() throws Exception { + String json = "{\"id\":\"1\",\"expr\":\"a + b + c\",\"unknown\":\"yo\"}"; + JSON.parseToObject(json, Expression.class); + // pass if no unexpected exception + } +} diff --git a/test/query/pojo/TestFilter.java b/test/query/pojo/TestFilter.java new file mode 100644 index 0000000000..e38088c09b --- /dev/null +++ b/test/query/pojo/TestFilter.java @@ -0,0 +1,97 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.pojo; + +import net.opentsdb.query.filter.TagVFilter; +import net.opentsdb.utils.JSON; + +import org.junit.Test; + +import java.util.Arrays; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class TestFilter { + + @Test(expected = IllegalArgumentException.class) + public void validationErrorWhenIdIsNull() throws Exception { + String json = "{\"id\":null}"; + Filter filter = JSON.parseToObject(json, Filter.class); + filter.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void validationBadId() throws Exception { + String json = "{\"id\":\"bad.Id\",\"tags\":[]}"; + Filter filter = JSON.parseToObject(json, Filter.class); + filter.validate(); + } + + @Test + public void deserialize() throws Exception { + String json = "{\"id\":\"f1\",\"tags\":[{\"tagk\":\"host\"," + + "\"filter\":\"*\",\"type\":\"iwildcard\",\"groupBy\":false}]}"; + + TagVFilter tag = new TagVFilter.Builder().setFilter("*").setGroupBy( + false) + .setTagk("host").setType("iwildcard").build(); + + Filter expectedFilter = Filter.Builder().setId("f1") + .setTags(Arrays.asList(tag)).build(); + + Filter filter = JSON.parseToObject(json, Filter.class); + filter.validate(); + assertEquals(expectedFilter, filter); + } + + @Test + public void serialize() throws Exception { + TagVFilter tag = new TagVFilter.Builder().setFilter("*").setGroupBy(false) + .setTagk("host").setType("iwildcard").build(); + + Filter filter = Filter.Builder().setId("f1") + .setTags(Arrays.asList(tag)).build(); + + String actual = JSON.serializeToString(filter); + assertTrue(actual.contains("\"id\":\"f1\"")); + assertTrue(actual.contains("\"tags\":[")); + assertTrue(actual.contains("\"tagk\":\"host\"")); + } + + @Test + public void unknownShouldBeIgnored() throws Exception { + String json = "{\"id\":\"1\",\"unknown\":\"yo\"}"; + JSON.parseToObject(json, Filter.class); + // pass if no unexpected exception + } + + @Test(expected = IllegalArgumentException.class) + public void invalidTags() throws Exception { + String json = "{\"id\":\"1\",\"tags\":[{\"tagk\":\"\"," + + "\"filter\":\"*\",\"type\":\"iwildcard\",\"group_by\":false}]," + + "\"aggregation\":{\"tags\":[\"appid\"],\"aggregator\":\"sum\"}}"; + + Filter filter = JSON.parseToObject(json, Filter.class); + filter.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void invalidAggregation() throws Exception { + String json = "{\"id\":\"1\",\"tags\":[{\"tagk\":\"\"," + + "\"filter\":\"*\",\"type\":\"iwildcard\",\"group_by\":false}]," + + "\"aggregator\":\"what\"}"; + Filter filter = JSON.parseToObject(json, Filter.class); + filter.validate(); + } +} diff --git a/test/query/pojo/TestJoin.java b/test/query/pojo/TestJoin.java new file mode 100644 index 0000000000..8b27a5d3ec --- /dev/null +++ b/test/query/pojo/TestJoin.java @@ -0,0 +1,66 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.pojo; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import net.opentsdb.query.expression.VariableIterator.SetOperator; +import net.opentsdb.utils.JSON; + +import org.junit.Test; + +public class TestJoin { + + @Test + public void deserialize() throws Exception { + final String json = "{\"operator\":\"union\"}"; + final Join join = Join.Builder().setOperator(SetOperator.UNION).build(); + final Join deserialized = JSON.parseToObject(json, Join.class); + assertEquals(join, deserialized); + } + + @Test + public void serialize() throws Exception { + final Join join = Join.Builder().setOperator(SetOperator.UNION).build(); + final String json = JSON.serializeToString(join); + assertTrue(json.contains("\"operator\":\"union\"")); + } + + @Test(expected = IllegalArgumentException.class) + public void validationErrorWhenOperatorIsNull() throws Exception { + final String json = "{\"operator\":null}"; + final Join join = JSON.parseToObject(json, Join.class); + join.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void validationErrorWhenOperatorIsEmpty() throws Exception { + final String json = "{\"operator\":\"\"}"; + final Join join = JSON.parseToObject(json, Join.class); + join.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void validationErrorWhenOperatorIsInvalid() throws Exception { + final String json = "{\"operator\":\"nosuchop\"}"; + final Join join = JSON.parseToObject(json, Join.class); + join.validate(); + } + + @Test + public void unknownShouldBeIgnored() throws Exception { + String json = "{\"operator\":\"intersection\",\"unknown\":\"yo\"}"; + JSON.parseToObject(json, Filter.class); + // pass if no unexpected exception + } +} diff --git a/test/query/pojo/TestMetric.java b/test/query/pojo/TestMetric.java new file mode 100644 index 0000000000..c312fc1b97 --- /dev/null +++ b/test/query/pojo/TestMetric.java @@ -0,0 +1,123 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.pojo; + +import net.opentsdb.core.FillPolicy; +import net.opentsdb.query.expression.NumericFillPolicy; +import net.opentsdb.utils.JSON; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class TestMetric { + @Test(expected = IllegalArgumentException.class) + public void validationErrorWhenMetricIsNull() throws Exception { + String json = "{\"id\":\"1\",\"filter\":\"2\"," + + "\"timeOffset\":\"1h-ago\",\"aggregator\":\"sum\"," + + "\"fillPolicy\":{\"policy\":\"nan\"}}"; + Metric metric = JSON.parseToObject(json, Metric.class); + metric.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void validationErrorWhenMetricIsEmpty() throws Exception { + String json = "{\"metric\":\"\",\"id\":\"1\",\"filter\":\"2\"," + + "\"timeOffset\":\"1h-ago\",\"aggregator\":\"sum\"," + + "\"fillPolicy\":{\"policy\":\"nan\"}}"; + Metric metric = JSON.parseToObject(json, Metric.class); + metric.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void validationErrorWhenIDIsNull() throws Exception { + String json = "{\"metric\":\"system.cpu\",\"id\":null,\"filter\":\"2\"," + + "\"timeOffset\":\"1h-ago\",\"aggregator\":\"sum\"," + + "\"fillPolicy\":{\"policy\":\"nan\"}}"; + Metric metric = JSON.parseToObject(json, Metric.class); + metric.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void validationErrorWhenIDIsEmpty() throws Exception { + String json = "{\"metric\":\"system.cpu\",\"id\":\"\",\"filter\":\"2\"," + + "\"timeOffset\":\"1h-ago\",\"aggregator\":\"sum\"," + + "\"fillPolicy\":{\"policy\":\"nan\"}}"; + Metric metric = JSON.parseToObject(json, Metric.class); + metric.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void validationErrorWhenIDIsInvalid() throws Exception { + String json = "{\"metric\":\"system.cpu\",\"id\":\"system.cpu\",\"filter\":\"2\"," + + "\"timeOffset\":\"1h-ago\",\"aggregator\":\"sum\"," + + "\"fillPolicy\":{\"policy\":\"nan\"}}"; + Metric metric = JSON.parseToObject(json, Metric.class); + metric.validate(); + } + + @Test + public void deserializeAllFields() throws Exception { + String json = "{\"metric\":\"YAMAS.cpu.idle\",\"id\":\"e1\",\"filter\":\"f2\"," + + "\"timeOffset\":\"1h-ago\",\"aggregator\":\"sum\"," + + "\"fillPolicy\":{\"policy\":\"nan\"}}"; + Metric metric = JSON.parseToObject(json, Metric.class); + metric.validate(); + Metric expectedMetric = Metric.Builder().setMetric("YAMAS.cpu.idle") + .setId("e1").setFilter("f2").setTimeOffset("1h-ago") + .setAggregator("sum") + .setFillPolicy(new NumericFillPolicy(FillPolicy.NOT_A_NUMBER)) + .build(); + + assertEquals(expectedMetric, metric); + } + + @Test + public void serialize() throws Exception { + Metric metric = Metric.Builder().setMetric("YAMAS.cpu.idle") + .setId("e1").setFilter("f2").setTimeOffset("1h-ago") + .setFillPolicy(new NumericFillPolicy(FillPolicy.NOT_A_NUMBER)) + .build(); + + String actual = JSON.serializeToString(metric); + assertTrue(actual.contains("\"metric\":\"YAMAS.cpu.idle\"")); + assertTrue(actual.contains("\"id\":\"e1\"")); + assertTrue(actual.contains("\"filter\":\"f2\"")); + assertTrue(actual.contains("\"timeOffset\":\"1h-ago\"")); + assertTrue(actual.contains("\"fillPolicy\":{")); + } + + @Test + public void unknownShouldBeIgnored() throws Exception { + String json = "{\"aggregator\":\"sum\",\"tags\":[\"foo\",\"bar\"],\"unknown\":\"garbage\"}"; + JSON.parseToObject(json, Metric.class); + // pass if no unexpected exception + } + + @Test(expected = IllegalArgumentException.class) + public void validationtErrorWhenTimeOffsetIsInvalid() throws Exception { + String json = "{\"metric\":\"YAMAS.cpu.idle\",\"id\":\"1\",\"filter\":\"2\"," + + "\"timeOffset\":\"what?\"}"; + Metric metric = JSON.parseToObject(json, Metric.class); + metric.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void validationtErrorBadFill() throws Exception { + String json = "{\"metric\":\"YAMAS.cpu.idle\",\"id\":\"1\",\"filter\":\"2\"," + + "\"fillPolicy\":{\"policy\":\"zero\",\"value\":42}}"; + Metric metric = JSON.parseToObject(json, Metric.class); + metric.validate(); + } +} diff --git a/test/query/pojo/TestOutput.java b/test/query/pojo/TestOutput.java new file mode 100644 index 0000000000..b4084e4b3e --- /dev/null +++ b/test/query/pojo/TestOutput.java @@ -0,0 +1,45 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.pojo; + +import net.opentsdb.utils.JSON; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class TestOutput { + @Test + public void deserializeAllFields() throws Exception { + String json = "{\"id\":\"m1\",\"alias\":\"CPU OK\"}"; + Output output = JSON.parseToObject(json, Output.class); + Output expectedOutput = Output.Builder().setId("m1").setAlias("CPU OK") + .build(); + assertEquals(expectedOutput, output); + } + + @Test + public void serialize() throws Exception { + Output output = Output.Builder().setId("m1").setAlias("CPU OK") + .build(); + String actual = JSON.serializeToString(output); + String expected = "{\"id\":\"m1\",\"alias\":\"CPU OK\"}"; + assertEquals(expected, actual); + } + + @Test + public void unknownFieldShouldBeIgnored() throws Exception { + String json = "{\"id\":\"m1\",\"unknown\":\"yo\"}"; + JSON.parseToObject(json, Filter.class); + // pass if no unexpected exception + } +} diff --git a/test/query/pojo/TestQuery.java b/test/query/pojo/TestQuery.java new file mode 100644 index 0000000000..ff8585d9c8 --- /dev/null +++ b/test/query/pojo/TestQuery.java @@ -0,0 +1,223 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.pojo; + +import net.opentsdb.core.FillPolicy; +import net.opentsdb.query.expression.NumericFillPolicy; +import net.opentsdb.query.expression.VariableIterator.SetOperator; +import net.opentsdb.query.filter.TagVFilter; +import net.opentsdb.utils.JSON; + +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class TestQuery { + Timespan time; + TagVFilter tag; + Filter filter; + Metric metric; + Expression expression; + Output output; + + String json = "{" + + " \"time\":{" + + " \"start\":\"3h-ago\"," + + " \"end\":\"1h-ago\"," + + " \"timezone\":\"UTC\"," + + " \"aggregator\":\"avg\"," + + " \"downsampler\":{\"interval\":\"15m\"," + + " \"aggregator\":\"avg\"," + + " \"fillPolicy\":{\"policy\":\"nan\"}}" + + " }," + + " \"filters\":[" + + " {" + + " \"id\":\"f1\"," + + " \"tags\":[" + + " {" + + " \"tagk\":\"host\"," + + " \"filter\":\"*\"," + + " \"type\":\"iwildcard\"," + + " \"groupBy\":false" + + " }" + + " ]" + + " }" + + " ]," + + " \"metrics\":[" + + " {" + + " \"metric\":\"YAMAS.cpu.idle\"," + + " \"id\":\"m1\"," + + " \"filter\":\"f1\"," + + " \"aggregator\":\"sum\"," + + " \"timeOffset\":\"0\"" + + " }" + + " ]," + + " \"expressions\":[" + + " {" + + " \"id\":\"e1\"," + + " \"expr\":\"a + b + c\"" + + " }" + + " ]," + + " \"outputs\":[" + + " {" + + " \"id\":\"m1\"," + + " \"alias\":\"CPU Idle EAST DC\"" + + " }" + + " ]" + + "}"; + + @Before + public void setup() { + time = Timespan.Builder().setStart("3h-ago").setAggregator("avg") + .setEnd("1h-ago").setTimezone("UTC").setDownsampler( + Downsampler.Builder().setInterval("15m").setAggregator("avg") + .setFillPolicy(new NumericFillPolicy(FillPolicy.NOT_A_NUMBER)).build()) + .build(); + TagVFilter tag = new TagVFilter.Builder().setFilter("*").setGroupBy( + false) + .setTagk("host").setType("iwildcard").build(); + filter = Filter.Builder().setId("f1").setTags(Arrays.asList(tag)).build(); + metric = Metric.Builder().setMetric("YAMAS.cpu.idle") + .setId("m1").setFilter("f1").setTimeOffset("0") + .setAggregator("sum").build(); + expression = Expression.Builder().setId("e1") + .setExpression("a + b + c").setJoin( + Join.Builder().setOperator(SetOperator.UNION).build()).build(); + output = Output.Builder().setId("m1").setAlias("CPU Idle EAST DC") + .build(); + } + + @Test(expected = IllegalArgumentException.class) + public void validationErrorWhenTimeIsNull() throws Exception { + Query query = getDefaultQueryBuilder().setTime(null).build(); + query.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void invalidTime() throws Exception { + Timespan invalidTime = Timespan.Builder().build(); + Query query = getDefaultQueryBuilder().setTime(invalidTime).build(); + query.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void metricsIsNull() throws Exception { + Query query = getDefaultQueryBuilder().setMetrics(null).build(); + query.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void metricsIsEmpty() throws Exception { + Query query = getDefaultQueryBuilder().setMetrics( + Collections.emptyList()).build(); + query.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void invalidMetric() throws Exception { + Metric invalidMetric = Metric.Builder().build(); + Query query = getDefaultQueryBuilder() + .setMetrics(Arrays.asList(invalidMetric)).build(); + query.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void invalidFilter() throws Exception { + Filter invalidFilter = Filter.Builder().build(); + Query query = getDefaultQueryBuilder() + .setFilters(Arrays.asList(invalidFilter)).build(); + query.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void invalidExpression() throws Exception { + Expression invalidExpression = Expression.Builder().build(); + Query query = getDefaultQueryBuilder() + .setExpressions(Arrays.asList(invalidExpression)).build(); + query.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void noSuchFilterIdInMetric() throws Exception { + Metric invalid_metric = Metric.Builder().setMetric("YAMAS.cpu.idle") + .setId("m2").setFilter("f2").setTimeOffset("0").build(); + Query query = getDefaultQueryBuilder().setMetrics( + Arrays.asList(invalid_metric, metric)).build(); + query.validate(); + } + + @Test + public void deserialize() throws Exception { + Query query = JSON.parseToObject(json, Query.class); + query.validate(); + Query expected = Query.Builder().setExpressions(Arrays.asList(expression)) + .setFilters(Arrays.asList(filter)).setMetrics(Arrays.asList(metric)) + .setTime(time).setOutputs(Arrays.asList(output)).build(); + assertEquals(expected, query); + } + + @Test(expected = IllegalArgumentException.class) + public void duplicatedFilterId() throws Exception { + Query query = getDefaultQueryBuilder().setFilters( + Arrays.asList(filter, filter)).build(); + query.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void duplicatedExpressionId() throws Exception { + Query query = getDefaultQueryBuilder().setExpressions( + Arrays.asList(expression, expression)).build(); + query.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void duplicatedMetricId() throws Exception { + Query query = getDefaultQueryBuilder().setMetrics( + Arrays.asList(metric, metric)).build(); + query.validate(); + } + + @Test + public void serialize() throws Exception { + Query query = Query.Builder().setExpressions(Arrays.asList(expression)) + .setFilters(Arrays.asList(filter)).setMetrics(Arrays.asList(metric)) + .setName("q1").setTime(time).setOutputs(Arrays.asList(output)).build(); + + String actual = JSON.serializeToString(query); +// String expected = "{\"name\":\"q1\",\"time\":{\"start\":\"3h-ago\"," +// + "\"end\":\"1h-ago\",\"timezone\":\"UTC\",\"downsample\":\"15m-avg-nan\"," +// + "\"interpolation\":\"LERP\"},\"filters\":[{\"id\":\"f1\"," +// + "\"tags\":[{\"tagk\":\"host\",\"filter\":\"*\",\"group_by\":false," +// + "\"type\":\"iwildcard\"}],\"aggregator\":\"sum\"}]," +// + "\"metrics\":[{\"metric\":\"YAMAS.cpu.idle\"," +// + "\"id\":\"m1\",\"filter\":\"f1\",\"time_offset\":\"0\"}]," +// + "\"expressions\":[{\"id\":\"e1\",\"expr\":\"a + b + c\"}]," +// + "\"outputs\":[{\"var\":\"q1.m1\",\"alias\":\"CPU Idle EAST DC\"}]}"; + assertTrue(actual.contains("\"name\":\"q1\"")); + assertTrue(actual.contains("\"start\":\"3h-ago\"")); + assertTrue(actual.contains("\"end\":\"1h-ago\"")); + assertTrue(actual.contains("\"timezone\":\"UTC\"")); + // TODO - finish the assertions + } + + private Query.Builder getDefaultQueryBuilder() { + return Query.Builder().setExpressions(Arrays.asList(expression)) + .setFilters(Arrays.asList(filter)).setMetrics(Arrays.asList(metric)) + .setName("q1").setTime(time).setOutputs(Arrays.asList(output)); + } +} diff --git a/test/query/pojo/TestTimeSpan.java b/test/query/pojo/TestTimeSpan.java new file mode 100644 index 0000000000..ecd1450d72 --- /dev/null +++ b/test/query/pojo/TestTimeSpan.java @@ -0,0 +1,137 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.pojo; + +import net.opentsdb.core.FillPolicy; +import net.opentsdb.query.expression.NumericFillPolicy; +import net.opentsdb.utils.JSON; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class TestTimeSpan { + @Test(expected = IllegalArgumentException.class) + public void startIsNull() { + String json = "{\"start\":null,\"end\":\"2015/05/05\"," + + "\"timezone\":\"UTC\",\"downsample\":\"15m-avg-nan\"," + + ",\"aggregator\":\"sum\"}"; + Timespan timespan = JSON.parseToObject(json, Timespan.class); + timespan.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void startIsEmpty() { + String json = "{\"start\":\"\",\"end\":\"2015/05/05\"," + + "\"timezone\":\"UTC\",\"downsample\":\"15m-avg-nan\"" + + ",\"aggregator\":\"sum\"}"; + Timespan timespan = JSON.parseToObject(json, Timespan.class); + timespan.validate(); + } + + @Test + public void endIsNull() { + String json = "{\"start\":\"2015/05/05\",\"end\":null," + + "\"timezone\":\"UTC\",\"downsample\":\"15m-avg-nan\"" + + ",\"aggregator\":\"sum\"}"; + Timespan timespan = JSON.parseToObject(json, Timespan.class); + timespan.validate(); + } + + @Test + public void endIsEmpty() { + String json = "{\"start\":\"1h-ago\",\"end\":\"\"," + + "\"timezone\":\"UTC\",\"downsample\":\"15m-avg-nan\"" + + ",\"aggregator\":\"sum\"}"; + Timespan timespan = JSON.parseToObject(json, Timespan.class); + timespan.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void aggregatorIsNull() { + String json = "{\"start\":\"1h-ago\",\"end\":\"2015/05/05\"," + + "\"timezone\":\"UTC\",\"downsample\":\"15m-avg-nan\"," + + "}"; + Timespan timespan = JSON.parseToObject(json, Timespan.class); + timespan.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void aggregatorIsEmpty() { + String json = "{\"start\":\"1h-ago\",\"end\":\"2015/05/05\"," + + "\"timezone\":\"UTC\",\"downsample\":\"15m-avg-nan\"" + + ",\"aggregator\":\"\"}"; + Timespan timespan = JSON.parseToObject(json, Timespan.class); + timespan.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void idIsNull() { + String json = "{\"start\":\"-1h\",\"end\":\"2015/05/05\"," + + "\"timezone\":\"UTC\",\"downsample\":\"15m-avg-nan\"" + + ",\"interpolation\":\"LERP\"}"; + Timespan timespan = JSON.parseToObject(json, Timespan.class); + timespan.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void idIsEmpty() { + String json = "{\"start\":\"-1h\",\"end\":\"2015/05/05\"," + + "\"timezone\":\"UTC\",\"downsample\":\"15m-avg-nan\"" + + ",\"interpolation\":\"LERP\"}"; + Timespan timespan = JSON.parseToObject(json, Timespan.class); + timespan.validate(); + } + + @Test(expected = IllegalArgumentException.class) + public void invalidDownsample() { + String json = "{\"start\":\"1h-ago\",\"end\":\"2015/05/05\",\"timezone\":\"UTC\"," + + "\"downsampler\":\"xxx\"}"; + Timespan timespan = JSON.parseToObject(json, Timespan.class); + timespan.validate(); + } + + @Test + public void deserialize() { + String json = "{\"start\":\"1h-ago\",\"end\":\"2015/05/05\",\"timezone\":\"UTC\"," + + "\"downsampler\":{\"interval\":\"15m\",\"aggregator\":\"avg\"," + + "\"fillPolicy\":{\"policy\":\"nan\"}},\"aggregator\":\"sum\"," + + "\"unknownfield\":\"boo\"}"; + Timespan timespan = JSON.parseToObject(json, Timespan.class); + Timespan expected = Timespan.Builder().setStart("1h-ago") + .setEnd("2015/05/05").setTimezone("UTC").setAggregator("sum") + .setDownsampler( + Downsampler.Builder().setInterval("15m").setAggregator("avg") + .setFillPolicy(new NumericFillPolicy(FillPolicy.NOT_A_NUMBER)).build()) + .build(); + timespan.validate(); + assertEquals(expected, timespan); + } + + @Test + public void serialize() { + Timespan timespan = Timespan.Builder().setStart("1h-ago") + .setEnd("2015/05/05").setTimezone("UTC").setAggregator("sum").setDownsampler( + Downsampler.Builder().setInterval("15m").setAggregator("avg") + .setFillPolicy(new NumericFillPolicy(FillPolicy.NOT_A_NUMBER)).build()) + .build(); + String actual = JSON.serializeToString(timespan); + assertTrue(actual.contains("\"start\":\"1h-ago\"")); + assertTrue(actual.contains("\"end\":\"2015/05/05\"")); + assertTrue(actual.contains("\"aggregator\":\"sum\"")); + assertTrue(actual.contains("\"timezone\":\"UTC\"")); + assertTrue(actual.contains("\"downsampler\":{")); + assertTrue(actual.contains("\"interval\":\"15m\"")); + } +} From 35717b127ef9cee78918bbdf04558c9564c9555d Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Thu, 5 Nov 2015 18:42:48 -0800 Subject: [PATCH 068/233] Add the ExpressionIterator. For now it uses Apache Jexl as the expression engine for simple operations like + - / *. We'll likely toss it with a real parser that can also handle functions. And add JGrapht for DAG creation used in the next commit. Also fix up the make file. Signed-off-by: Chris Larsen --- Makefile.am | 11 +- pom.xml.in | 12 + src/query/expression/ExpressionIterator.java | 417 +++++++ .../expression/TestExpressionIterator.java | 1095 +++++++++++++++++ third_party/include.mk | 2 + third_party/jexl/commons-jexl-2.1.1.jar.md5 | 1 + .../jexl/commons-logging-1.1.1.jar.md5 | 1 + third_party/jexl/include.mk | 33 + third_party/jgrapht/include.mk | 23 + .../jgrapht/jgrapht-core-0.9.1.jar.md5 | 1 + 10 files changed, 1594 insertions(+), 2 deletions(-) create mode 100644 src/query/expression/ExpressionIterator.java create mode 100644 test/query/expression/TestExpressionIterator.java create mode 100644 third_party/jexl/commons-jexl-2.1.1.jar.md5 create mode 100644 third_party/jexl/commons-logging-1.1.1.jar.md5 create mode 100644 third_party/jexl/include.mk create mode 100644 third_party/jgrapht/include.mk create mode 100644 third_party/jgrapht/jgrapht-core-0.9.1.jar.md5 diff --git a/Makefile.am b/Makefile.am index d9d7e5b29c..113cb60954 100644 --- a/Makefile.am +++ b/Makefile.am @@ -78,6 +78,7 @@ tsdb_SRC := \ src/query/expression/Expression.java \ src/query/expression/ExpressionDataPoint.java \ src/query/expression/ExpressionFactory.java \ + src/query/expression/ExpressionIterator.java \ src/query/expression/ExpressionReader.java \ src/query/expression/Expressions.java \ src/query/expression/ExpressionTree.java \ @@ -91,12 +92,13 @@ tsdb_SRC := \ src/query/expression/Scale.java \ src/query/expression/TimeSyncedIterator.java \ src/query/expression/UnionIterator.java \ + src/query/expression/VariableIterator.java \ src/query/filter/TagVFilter.java \ src/query/filter/TagVLiteralOrFilter.java \ src/query/filter/TagVNotKeyFilter.java \ src/query/filter/TagVNotLiteralOrFilter.java \ src/query/filter/TagVRegexFilter.java \ - src/query/filter/VariableIterator.java \ + src/query/filter/TagVWildcardFilter.java \ src/query/pojo/Downsampler.java \ src/query/pojo/Expression.java \ src/query/pojo/Filter.java \ @@ -180,6 +182,7 @@ tsdb_SRC := \ src/utils/Threads.java tsdb_DEPS = \ + $(COMMONS_LOGGING) \ $(GUAVA) \ $(LOG4J_OVER_SLF4J) \ $(LOGBACK_CLASSIC) \ @@ -188,6 +191,8 @@ tsdb_DEPS = \ $(JACKSON_CORE) \ $(JACKSON_DATABIND) \ $(JAVACC) \ + $(JEXL) \ + $(JGRAPHT) \ $(NETTY) \ $(SLF4J_API) \ $(SUASYNC) \ @@ -259,6 +264,7 @@ test_SRC := \ test/query/expression/BaseTimeSyncedIteratorTest.java \ test/query/expression/TestAbsolute.java \ test/query/expression/TestExpressionFactory.java \ + test/query/expression/TestExpressionIterator.java \ test/query/expression/TestExpressionReader.java \ test/query/expression/TestExpressions.java \ test/query/expression/TestExpressionTree.java \ @@ -285,7 +291,6 @@ test_SRC := \ test/query/pojo/TestOutput.java \ test/query/pojo/TestQuery.java \ test/query/pojo/TestTimespan.java \ - test/query/pojo/TestValidatable.java \ test/search/TestSearchPlugin.java \ test/search/TestSearchQuery.java \ test/search/TestTimeSeriesLookup.java \ @@ -769,6 +774,8 @@ pom.xml: pom.xml.in Makefile -e 's/@SUASYNC_VERSION@/$(SUASYNC_VERSION)/' \ -e 's/@ZOOKEEPER_VERSION@/$(ZOOKEEPER_VERSION)/' \ -e 's/@APACHE_MATH_VERSION@/$(APACHE_MATH_VERSION)/' \ + -e 's/@JEXL_VERSION@/$(JEXL_VERSION)/' \ + -e 's/@JGRAPHT_VERSION@/$(JGRAPHT_VERSION)/' \ -e 's/@spec_title@/$(spec_title)/' \ -e 's/@spec_vendor@/$(spec_vendor)/' \ -e 's/@spec_version@/$(PACKAGE_VERSION)/' \ diff --git a/pom.xml.in b/pom.xml.in index 8e37d22217..e5914cd031 100644 --- a/pom.xml.in +++ b/pom.xml.in @@ -388,6 +388,18 @@ commons-math3 @APACHE_MATH_VERSION@ + + + org.apache.commons + commons-jexl + @JEXL_VERSION@ + + + + org.jgrapht + jgrapht-core + @JGRAPHT_VERSION@ + diff --git a/src/query/expression/ExpressionIterator.java b/src/query/expression/ExpressionIterator.java new file mode 100644 index 0000000000..c7d12c78ba --- /dev/null +++ b/src/query/expression/ExpressionIterator.java @@ -0,0 +1,417 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import net.opentsdb.core.FillPolicy; +import net.opentsdb.query.expression.VariableIterator.SetOperator; +import net.opentsdb.utils.ByteSet; + +import org.apache.commons.jexl2.JexlContext; +import org.apache.commons.jexl2.JexlEngine; +import org.apache.commons.jexl2.MapContext; +import org.apache.commons.jexl2.Script; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableSet; + +/** + * A iterator that applies an expression to the results of multiple sub queries. + * To use this class: + * - Instantiate with a valid expression + * - Call {@link #getVariableNames()} and iterate over a set of TSSubQueries and + * their results. For each query that matches a variable name, call + * {@link #addResults()} with the result set. + * - Call {@link #compile()} to setup the meta data, fills and compute the + * intersection of the series. + * - Call {@link #values()} and store the reference. Results for each + * series will be written here as you iterate. + * - Call {@link #hasNext()} and {@link #next()} to iterate over results. + * - At each iteration, fetch the timestamp and value from the data points array. + *

+ * Iteration is performed across all series supplied to the iterator, synchronizing + * on the timestamps and substituting fill values where appropriate. + *

+ * WARNING: You MUST supply a result set and associated sub query to match each + * of the variable names in the expression. If you fail to do so, when you call + * {@link #compile()} you'll get an exception. + *

+ * NOTE: Right now this class only supports intersection on the series so that + * each metric result must contain series with the same tags based on the flags + * provided in the ctor. + * NOTE: If a result set doesn't include a fill policy, we default to ZERO for + * "missing" data points. + */ +public class ExpressionIterator implements ITimeSyncedIterator { + private static final Logger LOG = LoggerFactory.getLogger(ExpressionIterator.class); + + /** Docs don't say whether this is thread safe or not. SOME methods are marked + * as not thread safe, so I assume it's ok to instantiate one of these guys + * and keep creating scripts from it. + */ + private final static JexlEngine JEXL_ENGINE = new JexlEngine(); + + /** Whether or not to intersect on the query tagks instead of the result set + * tagks */ + private final boolean intersect_on_query_tagks; + + /** Whether or not to include the aggregated tags in the result set */ + private final boolean include_agg_tags; + + /** List of iterators and their IDs */ + private final Map results; + + /** The compiled expression */ + private final Script expression; + + /** The context where we'll dump results for processing through the expression */ + private final JexlContext context = new MapContext(); + + /** A list of unique variable names pulled from the expression */ + private final Set names; + + /** The intersection iterator we'll use for processing */ + // TODO - write an interface to allow other set operators, e.g. union, disjoint + private VariableIterator iterator; + + /** A map of results from the intersection iterator to pass to the expression */ + private Map iteration_results; + + /** The results of processing the expressions */ + private ExpressionDataPoint[] dps; + + /** The ID of this iterator */ + private final String id; + + /** The index of this iterator in expressions */ + private int index; + + /** A fill policy for this expression if data is missing */ + private NumericFillPolicy fill_policy; + + /** The set operator to use for joining sets */ + private SetOperator set_operator; + + // NOTE - if the query is set to NONE for the aggregation and the query has + // no tagk filters then we shouldn't set the II's intersect_on_query_tagks + /** + * Default Ctor that compiles the expression for use with this iterator. + * @param expression The expression to compile and use + * @param set_operator The type of set operator to use + * @param intersect_on_query_tagks Whether or not to include only the query + * specified tags during intersection + * @param include_agg_tags Whether or not to include aggregated tags during + * intersection + * @throws IllegalArgumentException if the expression is null or empty or doesn't + * contain any variables. + * @throws JexlException if the expression isn't valid + */ + public ExpressionIterator(final String id, final String expression, + final SetOperator set_operator, + final boolean intersect_on_query_tagks, final boolean include_agg_tags) { + if (expression == null || expression.isEmpty()) { + throw new IllegalArgumentException("The expression cannot be null"); + } + if (set_operator == null) { + throw new IllegalArgumentException("The set operator cannot be null"); + } + this.id = id; + this.intersect_on_query_tagks = intersect_on_query_tagks; + this.include_agg_tags = include_agg_tags; + results = new HashMap(); + this.expression = JEXL_ENGINE.createScript(expression); + names = new HashSet(); + extractVariableNames(); + if (names.size() < 1) { + throw new IllegalArgumentException( + "The expression didn't appear to have any variables"); + } + this.set_operator = set_operator; + fill_policy = new NumericFillPolicy(FillPolicy.NOT_A_NUMBER); + } + + /** + * Copy constructor that setups up a dupe of this iterator with fresh sub + * iterator objects for use in a nested expression. + * @param iterator The expression to copy from. + */ + private ExpressionIterator(final ExpressionIterator iterator) { + id = iterator.id; + // need to recompile, don't know if we'll run into threading issues + expression = JEXL_ENGINE.createScript(iterator.expression.toString()); + intersect_on_query_tagks = iterator.intersect_on_query_tagks; + include_agg_tags = iterator.include_agg_tags; + set_operator = iterator.set_operator; + + results = new HashMap(); + for (Entry entry : iterator.results.entrySet()) { + results.put(entry.getKey(), entry.getValue().getCopy()); + } + + names = new HashSet(); + extractVariableNames(); + if (names.size() < 1) { + throw new IllegalArgumentException( + "The expression didn't appear to have any variables"); + } + } + + @Override + public String toString() { + final StringBuffer buf = new StringBuffer(); + buf.append("ExpressionIterator(id=") + .append(id) + .append(", expression=\"") + .append(expression.toString()) + .append("\", VariableIterator=") + .append(iterator) + .append(", dps=") + .append(dps) + .append(", results=") + .append(results) + .append(")"); + return buf.toString(); + } + + /** + * Adds a sub query result object to the iterator. + * TODO - accept a proper object, not a map + * @param results The results to store. + * @throws IllegalArgumentException if the object is missing required data + */ + public void addResults(final String id, final ITimeSyncedIterator iterator) { + if (id == null) { + throw new IllegalArgumentException("Missing ID"); + } + if (iterator == null) { + throw new IllegalArgumentException("Iterator cannot be null"); + } + results.put(id, iterator); + } + + /** + * Builds the iterator by computing the intersection of all series in all sets + * and sets up the output. + * @throws IllegalArgumentException if there aren't any results, or we don't + * have a result for each variable, or something else is wrong. + * @throws IllegalDataException if no series were left after computing the + * intersection. + */ + public void compile() { + if (LOG.isDebugEnabled()) { + LOG.debug("Compiling " + this); + } + if (results.size() < 1) { + throw new IllegalArgumentException("Missing query results."); + } + if (results.size() < names.size()) { + throw new IllegalArgumentException("Not enough query results [" + + results.size() + "] for the expression variables [" + + names.size() + "]"); + } + + // don't care if we have extra results, but we had darned well better make + // sure we have a result set for each variable + for (final String variable : names) { + // validation + final ITimeSyncedIterator it = results.get(variable.toLowerCase()); + if (it == null) { + throw new IllegalArgumentException("Missing results for variable " + variable); + } + + if (it instanceof ExpressionIterator) { + ((ExpressionIterator)it).compile(); + } + if (LOG.isDebugEnabled()) { + LOG.debug("Matched variable " + variable + " to " + it); + } + } + + // TODO implement other set functions + switch (set_operator) { + case INTERSECTION: + iterator = new IntersectionIterator(id, results, intersect_on_query_tagks, + include_agg_tags); + break; + case UNION: + iterator = new UnionIterator(id, results, intersect_on_query_tagks, + include_agg_tags); + } + iteration_results = iterator.getResults(); + + dps = new ExpressionDataPoint[iterator.getSeriesSize()]; + for (int i = 0; i < iterator.getSeriesSize(); i++) { + final Iterator> it = + iteration_results.entrySet().iterator(); + Entry entry = it.next(); + + if (entry.getValue() == null || entry.getValue()[i] == null) { + dps[i] = new ExpressionDataPoint(); + } else { + dps[i] = new ExpressionDataPoint(entry.getValue()[i]); + } + while (it.hasNext()) { + entry = it.next(); + if (entry.getValue() != null && entry.getValue()[i] != null) { + dps[i].add(entry.getValue()[i]); + } + } + } + + if (LOG.isDebugEnabled()) { + LOG.debug("Finished compiling " + this); + } + } + + /** + * Checks to see if we have another value in any of the series. + * Make sure to call {@link #compile()} first. + * @return True if there is more data to process, false if not + */ + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + /** + * Fetches the next set of data and computes a value for the expression. + * Make sure to call {@link #compile()} first. + * And make sure to call {@link #hasNext()} before calling this. + * @return A link to the data points for this result set + * @throws IllegalDataException if there wasn't any data left in any of the + * series. + * @throws JexlException if something went pear shaped processing the expression + */ + public ExpressionDataPoint[] next(final long timestamp) { + + // fetch the timestamp ONCE to save some cycles. + // final long timestamp = iterator.nextTimestamp(); + iterator.next(); + + // set aside a couple of addresses for the variables + double val; + double result; + for (int i = 0; i < iterator.getSeriesSize(); i++) { + // this here is why life sucks. there MUST be a better way to bind variables + for (final String variable : names) { + if (iteration_results.get(variable)[i] == null) { + context.set(variable, results.get(variable).getFillPolicy().getValue()); + } else { + val = iteration_results.get(variable)[i].toDouble(); + if (Double.isNaN(val)) { + context.set(variable, results.get(variable).getFillPolicy().getValue()); + } else { + context.set(variable, val); + } + } + } + result = (Double)expression.execute(context); + dps[i].reset(timestamp, result); + } + return dps; + } + + /** @return a list of expression results. You can keep this list and check the + * results on each call to {@link #next()} */ + @Override + public ExpressionDataPoint[] values() { + return dps; + } + + /** + * Pulls the variable names from the expression and stores them in {@link #names} + */ + private void extractVariableNames() { + if (expression == null) { + throw new IllegalArgumentException("The expression was null"); + } + + for (final List exp_list : JEXL_ENGINE.getVariables(expression)) { + for (final String variable : exp_list) { + names.add(variable); + } + } + } + + /** @return an immutable set of the variable IDs used in the expression. Case + * sensitive. */ + public Set getVariableNames() { + return ImmutableSet.copyOf(names); + } + + public void setSetOperator(final SetOperator set_operator) { + this.set_operator = set_operator; + } + + @Override + public long nextTimestamp() { + return iterator.nextTimestamp(); + } + + @Override + public int size() { + return dps.length; + } + + @Override + public void nullIterator(int index) { + if (index < 0 || index >= dps.length) { + throw new IllegalArgumentException("Index out of bounds"); + } + // TODO - do it + } + + @Override + public int getIndex() { + return index; + } + + @Override + public void setIndex(int index) { + this.index = index; + } + + @Override + public String getId() { + return id; + } + + @Override + public ByteSet getQueryTagKs() { + return null; + } + + @Override + public void setFillPolicy(NumericFillPolicy policy) { + fill_policy = policy; + } + + @Override + public NumericFillPolicy getFillPolicy() { + return fill_policy; + } + + @Override + public ITimeSyncedIterator getCopy() { + final ExpressionIterator ei = new ExpressionIterator(this); + return ei; + } +} diff --git a/test/query/expression/TestExpressionIterator.java b/test/query/expression/TestExpressionIterator.java new file mode 100644 index 0000000000..9c98a5071c --- /dev/null +++ b/test/query/expression/TestExpressionIterator.java @@ -0,0 +1,1095 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import net.opentsdb.core.FillPolicy; +import net.opentsdb.core.IllegalDataException; +import net.opentsdb.query.expression.VariableIterator.SetOperator; + +import org.apache.commons.jexl2.JexlException; +import org.hbase.async.Bytes; +import org.junit.Test; + +public class TestExpressionIterator extends BaseTimeSyncedIteratorTest { + + @Test + public void ctor() throws Exception { + final ExpressionIterator exp = new ExpressionIterator("ei", "a + b", + SetOperator.INTERSECTION, false, false); + assertEquals(2, exp.getVariableNames().size()); + assertTrue(exp.getVariableNames().contains("a")); + assertTrue(exp.getVariableNames().contains("b")); + assertFalse(exp.getVariableNames().contains("+")); // I'm not a variable :( + assertNull(exp.values()); + } + + @Test (expected = IllegalArgumentException.class) + public void ctorNoVariables() throws Exception { + new ExpressionIterator("ei", "1 + 1", SetOperator.INTERSECTION, false, false); + } + + @Test (expected = IllegalArgumentException.class) + public void ctorNullExpression() throws Exception { + new ExpressionIterator("ei", null, SetOperator.INTERSECTION, false, false); + } + + @Test (expected = JexlException.class) + public void ctorBadExpression() throws Exception { + new ExpressionIterator("ei", " a / ", SetOperator.INTERSECTION, false, false); + } + + @Test (expected = IllegalArgumentException.class) + public void ctorEmptyExpression() throws Exception { + new ExpressionIterator("ei", "", SetOperator.INTERSECTION, false, false); + } + + @Test (expected = IllegalArgumentException.class) + public void ctorNullOperator() throws Exception { + new ExpressionIterator("ei", "a + b", null, false, false); + } + + @Test + public void aPlusBWithTwoSeries() throws Exception { + oneExtraSameE(); + queryAB_Dstar(); + remapResults(); + + ExpressionIterator exp = new ExpressionIterator("ei", "a + b", + SetOperator.INTERSECTION, false, false); + exp.addResults("a", iterators.get("a")); + exp.addResults("b", iterators.get("b")); + + exp.compile(); + final ExpressionDataPoint[] dps = exp.values(); + assertEquals(2, dps.length); + validateMeta(dps, true); + + long ts = 1431561600000L; + double[] values = new double[] { 12, 18 }; + long its = exp.nextTimestamp(); + while (exp.hasNext()) { + exp.next(its); + + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(values[0], dps[0].toDouble(), 0.0001); + assertEquals(values[1], dps[1].toDouble(), 0.0001); + + values[0] += 2; + values[1] += 2; + ts += 60000; + its = exp.nextTimestamp(); + } + + for (int i = 0; i < dps.length; i++) { + assertEquals(2, dps[i].tags().size()); + assertTrue(dps[i].aggregatedTags().isEmpty()); + } + assertArrayEquals(TAGV_UIDS.get("D"), dps[0].tags().get(TAGV_UIDS.get("D"))); + assertArrayEquals(TAGV_UIDS.get("F"), dps[1].tags().get(TAGV_UIDS.get("D"))); + } + + @Test + public void aMinusBWithTwoSeries() throws Exception { + oneExtraSameE(); + queryAB_Dstar(); + remapResults(); + + ExpressionIterator exp = new ExpressionIterator("ei", "a - b", + SetOperator.INTERSECTION, false, false); + exp.addResults("a", iterators.get("a")); + exp.addResults("b", iterators.get("b")); + + exp.compile(); + final ExpressionDataPoint[] dps = exp.values(); + assertEquals(2, dps.length); + validateMeta(dps, true); + + long ts = 1431561600000L; + long its = exp.nextTimestamp(); + while (exp.hasNext()) { + exp.next(its); + + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(-10, dps[0].toDouble(), 0.0001); + assertEquals(-10, dps[1].toDouble(), 0.0001); + ts += 60000; + its = exp.nextTimestamp(); + } + + for (int i = 0; i < dps.length; i++) { + assertEquals(2, dps[i].tags().size()); + assertTrue(dps[i].aggregatedTags().isEmpty()); + } + assertArrayEquals(TAGV_UIDS.get("D"), dps[0].tags().get(TAGV_UIDS.get("D"))); + assertArrayEquals(TAGV_UIDS.get("F"), dps[1].tags().get(TAGV_UIDS.get("D"))); + } + + @Test + public void aTimesBWithTwoSeries() throws Exception { + oneExtraSameE(); + queryAB_Dstar(); + remapResults(); + + ExpressionIterator exp = new ExpressionIterator("ei", "a * b", + SetOperator.INTERSECTION, false, false); + exp.addResults("a", iterators.get("a")); + exp.addResults("b", iterators.get("b")); + + exp.compile(); + final ExpressionDataPoint[] dps = exp.values(); + assertEquals(2, dps.length); + validateMeta(dps, true); + + long ts = 1431561600000L; + long its = exp.nextTimestamp(); + exp.next(its); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(11, dps[0].toDouble(), 0.0001); + assertEquals(56, dps[1].toDouble(), 0.0001); + ts += 60000; + + its = exp.nextTimestamp(); + exp.next(its); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(24, dps[0].toDouble(), 0.0001); + assertEquals(75, dps[1].toDouble(), 0.0001); + ts += 60000; + + its = exp.nextTimestamp(); + exp.next(its); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(39, dps[0].toDouble(), 0.0001); + assertEquals(96, dps[1].toDouble(), 0.0001); + + assertFalse(exp.hasNext()); + + for (int i = 0; i < dps.length; i++) { + assertEquals(2, dps[i].tags().size()); + assertTrue(dps[i].aggregatedTags().isEmpty()); + } + assertArrayEquals(TAGV_UIDS.get("D"), dps[0].tags().get(TAGV_UIDS.get("D"))); + assertArrayEquals(TAGV_UIDS.get("F"), dps[1].tags().get(TAGV_UIDS.get("D"))); + } + + @Test + public void aDivideBWithTwoSeries() throws Exception { + oneExtraSameE(); + queryAB_Dstar(); + remapResults(); + + ExpressionIterator exp = new ExpressionIterator("ei", "a / b", + SetOperator.INTERSECTION, false, false); + exp.addResults("a", iterators.get("a")); + exp.addResults("b", iterators.get("b")); + + exp.compile(); + final ExpressionDataPoint[] dps = exp.values(); + assertEquals(2, dps.length); + validateMeta(dps, true); + + long ts = 1431561600000L; + long its = exp.nextTimestamp(); + exp.next(its); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(0.0909, dps[0].toDouble(), 0.0001); + assertEquals(0.2857, dps[1].toDouble(), 0.0001); + ts += 60000; + + its = exp.nextTimestamp(); + exp.next(its); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(0.1666, dps[0].toDouble(), 0.0001); + assertEquals(0.3333, dps[1].toDouble(), 0.0001); + ts += 60000; + + its = exp.nextTimestamp(); + exp.next(its); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(0.2307, dps[0].toDouble(), 0.0001); + assertEquals(0.375, dps[1].toDouble(), 0.0001); + + assertFalse(exp.hasNext()); + + for (int i = 0; i < dps.length; i++) { + assertEquals(2, dps[i].tags().size()); + assertTrue(dps[i].aggregatedTags().isEmpty()); + } + assertArrayEquals(TAGV_UIDS.get("D"), dps[0].tags().get(TAGV_UIDS.get("D"))); + assertArrayEquals(TAGV_UIDS.get("F"), dps[1].tags().get(TAGV_UIDS.get("D"))); + } + + @Test + public void aModBWithTwoSeries() throws Exception { + oneExtraSameE(); + queryAB_Dstar(); + remapResults(); + + ExpressionIterator exp = new ExpressionIterator("ei", "a % b", + SetOperator.INTERSECTION, false, false); + exp.addResults("a", iterators.get("a")); + exp.addResults("b", iterators.get("b")); + + exp.compile(); + final ExpressionDataPoint[] dps = exp.values(); + assertEquals(2, dps.length); + validateMeta(dps, true); + + long ts = 1431561600000L; + long its = exp.nextTimestamp(); + double[] values = new double[] { 1, 4 }; + while (exp.hasNext()) { + exp.next(its); + + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(values[0]++, dps[0].toDouble(), 0.0001); + assertEquals(values[1]++, dps[1].toDouble(), 0.0001); + ts += 60000; + its = exp.nextTimestamp(); + } + + for (int i = 0; i < dps.length; i++) { + assertEquals(2, dps[i].tags().size()); + assertTrue(dps[i].aggregatedTags().isEmpty()); + } + assertArrayEquals(TAGV_UIDS.get("D"), dps[0].tags().get(TAGV_UIDS.get("D"))); + assertArrayEquals(TAGV_UIDS.get("F"), dps[1].tags().get(TAGV_UIDS.get("D"))); + } + + @Test + public void aDivideByZeroWithTwoSeries() throws Exception { + oneExtraSameE(); + queryAB_Dstar(); + remapResults(); + + // Jexl apparently happily allows this, just emits a zero + ExpressionIterator exp = new ExpressionIterator("ei", "a / 0", + SetOperator.INTERSECTION, false, false); + exp.addResults("a", iterators.get("a")); + exp.addResults("b", iterators.get("b")); + + exp.compile(); + final ExpressionDataPoint[] dps = exp.values(); + assertEquals(2, dps.length); + validateMeta(dps, true); + + long ts = 1431561600000L; + long its = exp.nextTimestamp(); + while (exp.hasNext()) { + exp.next(its); + + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(0, dps[0].toDouble(), 0.0001); + assertEquals(0, dps[1].toDouble(), 0.0001); + ts += 60000; + its = exp.nextTimestamp(); + } + + for (int i = 0; i < dps.length; i++) { + assertEquals(2, dps[i].tags().size()); + assertTrue(dps[i].aggregatedTags().isEmpty()); + } + assertArrayEquals(TAGV_UIDS.get("D"), dps[0].tags().get(TAGV_UIDS.get("D"))); + assertArrayEquals(TAGV_UIDS.get("F"), dps[1].tags().get(TAGV_UIDS.get("D"))); + } + + @Test + public void doubleVariableAndPrecedence() throws Exception { + oneExtraSameE(); + queryAB_Dstar(); + remapResults(); + + ExpressionIterator exp = new ExpressionIterator("ei", "a + (b * b)", + SetOperator.INTERSECTION, false, false); + exp.addResults("a", iterators.get("a")); + exp.addResults("b", iterators.get("b")); + + exp.compile(); + final ExpressionDataPoint[] dps = exp.values(); + assertEquals(2, dps.length); + validateMeta(dps, true); + + long ts = 1431561600000L; + long its = exp.nextTimestamp(); + exp.next(its); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(122, dps[0].toDouble(), 0.0001); + assertEquals(200, dps[1].toDouble(), 0.0001); + ts += 60000; + + its = exp.nextTimestamp(); + exp.next(its); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(146, dps[0].toDouble(), 0.0001); + assertEquals(230, dps[1].toDouble(), 0.0001); + ts += 60000; + + its = exp.nextTimestamp(); + exp.next(its); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(172, dps[0].toDouble(), 0.0001); + assertEquals(262, dps[1].toDouble(), 0.0001); + + assertFalse(exp.hasNext()); + + for (int i = 0; i < dps.length; i++) { + assertEquals(2, dps[i].tags().size()); + assertTrue(dps[i].aggregatedTags().isEmpty()); + } + assertArrayEquals(TAGV_UIDS.get("D"), dps[0].tags().get(TAGV_UIDS.get("D"))); + assertArrayEquals(TAGV_UIDS.get("F"), dps[1].tags().get(TAGV_UIDS.get("D"))); + } + + @Test + public void doubleVariableAndPrecedenceChanged() throws Exception { + oneExtraSameE(); + queryAB_Dstar(); + remapResults(); + + ExpressionIterator exp = new ExpressionIterator("ei", "(a + b) * b", + SetOperator.INTERSECTION, false, false); + exp.addResults("a", iterators.get("a")); + exp.addResults("b", iterators.get("b")); + + exp.compile(); + final ExpressionDataPoint[] dps = exp.values(); + assertEquals(2, dps.length); + validateMeta(dps, true); + + long ts = 1431561600000L; + long its = exp.nextTimestamp(); + exp.next(its); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(132, dps[0].toDouble(), 0.0001); + assertEquals(252, dps[1].toDouble(), 0.0001); + ts += 60000; + + its = exp.nextTimestamp(); + exp.next(its); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(168, dps[0].toDouble(), 0.0001); + assertEquals(300, dps[1].toDouble(), 0.0001); + ts += 60000; + + its = exp.nextTimestamp(); + exp.next(its); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(208, dps[0].toDouble(), 0.0001); + assertEquals(352, dps[1].toDouble(), 0.0001); + + assertFalse(exp.hasNext()); + + for (int i = 0; i < dps.length; i++) { + assertEquals(2, dps[i].tags().size()); + assertTrue(dps[i].aggregatedTags().isEmpty()); + } + assertArrayEquals(TAGV_UIDS.get("D"), dps[0].tags().get(TAGV_UIDS.get("D"))); + assertArrayEquals(TAGV_UIDS.get("F"), dps[1].tags().get(TAGV_UIDS.get("D"))); + } + + @Test + public void aPlusScalarDropB() throws Exception { + oneExtraSameE(); + queryAB_Dstar(); + remapResults(); + + ExpressionIterator exp = new ExpressionIterator("ei", "a + 1", + SetOperator.INTERSECTION, false, false); + exp.addResults("a", iterators.get("a")); + exp.addResults("b", iterators.get("b")); + + exp.compile(); + final ExpressionDataPoint[] dps = exp.values(); + assertEquals(2, dps.length); + validateMeta(dps, true); + + long ts = 1431561600000L; + double[] values = new double[] { 2, 5 }; + long its = exp.nextTimestamp(); + while (exp.hasNext()) { + exp.next(its); + + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(values[0]++, dps[0].toDouble(), 0.0001); + assertEquals(values[1]++, dps[1].toDouble(), 0.0001); + ts += 60000; + its = exp.nextTimestamp(); + } + + for (int i = 0; i < dps.length; i++) { + assertEquals(2, dps[i].tags().size()); + assertTrue(dps[i].aggregatedTags().isEmpty()); + } + assertArrayEquals(TAGV_UIDS.get("D"), dps[0].tags().get(TAGV_UIDS.get("D"))); + assertArrayEquals(TAGV_UIDS.get("F"), dps[1].tags().get(TAGV_UIDS.get("D"))); + } + + @Test (expected = IllegalArgumentException.class) + public void missingRequiredVariable() throws Exception { + oneExtraSameE(); + queryAB_Dstar(); + remapResults(); + + ExpressionIterator exp = new ExpressionIterator("ei", "a + b + c", + SetOperator.INTERSECTION, false, false); + exp.addResults("a", iterators.get("a")); + exp.addResults("b", iterators.get("b")); + + exp.compile(); + } + + @Test + public void aPlusBMissingPointsDefaultFillZero() throws Exception { + threeSameEGaps(); + queryAB_Dstar(); + remapResults(); + + ExpressionIterator exp = new ExpressionIterator("ei", "a + b", + SetOperator.INTERSECTION, false, false); + exp.addResults("a", iterators.get("a")); + exp.addResults("b", iterators.get("b")); + + exp.compile(); + final ExpressionDataPoint[] dps = exp.values(); + assertEquals(3, dps.length); + validateMeta(dps, true); + + long ts = 1431561600000L; + long its = exp.nextTimestamp(); + exp.next(its); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(ts, dps[2].timestamp()); + assertEquals(1, dps[0].toDouble(), 0.0001); + assertEquals(4, dps[1].toDouble(), 0.0001); + assertEquals(0, dps[2].toDouble(), 0.0001); + ts += 60000; + + its = exp.nextTimestamp(); + exp.next(its); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(ts, dps[2].timestamp()); + assertEquals(0, dps[0].toDouble(), 0.0001); + assertEquals(20, dps[1].toDouble(), 0.0001); + assertEquals(8, dps[2].toDouble(), 0.0001); + ts += 60000; + + its = exp.nextTimestamp(); + exp.next(its); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(ts, dps[2].timestamp()); + assertEquals(16, dps[0].toDouble(), 0.0001); + assertEquals(0, dps[1].toDouble(), 0.0001); + assertEquals(28, dps[2].toDouble(), 0.0001); + + assertFalse(exp.hasNext()); + + for (int i = 0; i < dps.length; i++) { + assertEquals(2, dps[i].tags().size()); + assertTrue(dps[i].aggregatedTags().isEmpty()); + } + assertArrayEquals(TAGV_UIDS.get("D"), dps[0].tags().get(TAGV_UIDS.get("D"))); + assertArrayEquals(TAGV_UIDS.get("F"), dps[1].tags().get(TAGV_UIDS.get("D"))); + assertArrayEquals(TAGV_UIDS.get("G"), dps[2].tags().get(TAGV_UIDS.get("D"))); + } + + @Test + public void aPlusBMissingPointsFillOne() throws Exception { + threeSameEGaps(); + queryAB_Dstar(); + remapResults(); + + ExpressionIterator exp = new ExpressionIterator("ei", "a + b", + SetOperator.INTERSECTION, false, false); + exp.addResults("a", iterators.get("a")); + exp.addResults("b", iterators.get("b")); + iterators.get("a").setFillPolicy(new NumericFillPolicy(FillPolicy.SCALAR, 1)); + iterators.get("b").setFillPolicy(new NumericFillPolicy(FillPolicy.SCALAR, 1)); + + exp.compile(); + final ExpressionDataPoint[] dps = exp.values(); + assertEquals(3, dps.length); + validateMeta(dps, true); + + long ts = 1431561600000L; + long its = exp.nextTimestamp(); + exp.next(its); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(ts, dps[2].timestamp()); + assertEquals(2, dps[0].toDouble(), 0.0001); + assertEquals(5, dps[1].toDouble(), 0.0001); + assertEquals(2, dps[2].toDouble(), 0.0001); + ts += 60000; + + its = exp.nextTimestamp(); + exp.next(its); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(ts, dps[2].timestamp()); + assertEquals(2, dps[0].toDouble(), 0.0001); + assertEquals(20, dps[1].toDouble(), 0.0001); + assertEquals(9, dps[2].toDouble(), 0.0001); + ts += 60000; + + its = exp.nextTimestamp(); + exp.next(its); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(ts, dps[2].timestamp()); + assertEquals(16, dps[0].toDouble(), 0.0001); + assertEquals(2, dps[1].toDouble(), 0.0001); + assertEquals(28, dps[2].toDouble(), 0.0001); + + assertFalse(exp.hasNext()); + + for (int i = 0; i < dps.length; i++) { + assertEquals(2, dps[i].tags().size()); + assertTrue(dps[i].aggregatedTags().isEmpty()); + } + assertArrayEquals(TAGV_UIDS.get("D"), dps[0].tags().get(TAGV_UIDS.get("D"))); + assertArrayEquals(TAGV_UIDS.get("F"), dps[1].tags().get(TAGV_UIDS.get("D"))); + assertArrayEquals(TAGV_UIDS.get("G"), dps[2].tags().get(TAGV_UIDS.get("D"))); + } + + @Test + public void aPlusBMissingPointsFillInfectiousNaN() throws Exception { + threeSameEGaps(); + queryAB_Dstar(); + remapResults(); + + ExpressionIterator exp = new ExpressionIterator("ei", "a + b", + SetOperator.INTERSECTION, false, false); + exp.addResults("a", iterators.get("a")); + exp.addResults("b", iterators.get("b")); + iterators.get("a").setFillPolicy( + new NumericFillPolicy(FillPolicy.NOT_A_NUMBER, Double.NaN)); + iterators.get("b").setFillPolicy( + new NumericFillPolicy(FillPolicy.NOT_A_NUMBER, Double.NaN)); + + exp.compile(); + final ExpressionDataPoint[] dps = exp.values(); + assertEquals(3, dps.length); + validateMeta(dps, true); + + long ts = 1431561600000L; + long its = exp.nextTimestamp(); + exp.next(its); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(ts, dps[2].timestamp()); + assertTrue(Double.isNaN(dps[0].toDouble())); + assertTrue(Double.isNaN(dps[1].toDouble())); + assertTrue(Double.isNaN(dps[2].toDouble())); + ts += 60000; + + its = exp.nextTimestamp(); + exp.next(its); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(ts, dps[2].timestamp()); + assertTrue(Double.isNaN(dps[0].toDouble())); + assertEquals(20, dps[1].toDouble(), 0.0001); + assertTrue(Double.isNaN(dps[2].toDouble())); + ts += 60000; + + its = exp.nextTimestamp(); + exp.next(its); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(ts, dps[2].timestamp()); + assertEquals(16, dps[0].toDouble(), 0.0001); + assertTrue(Double.isNaN(dps[1].toDouble())); + assertEquals(28, dps[2].toDouble(), 0.0001); + + assertFalse(exp.hasNext()); + + for (int i = 0; i < dps.length; i++) { + assertEquals(2, dps[i].tags().size()); + assertTrue(dps[i].aggregatedTags().isEmpty()); + } + assertArrayEquals(TAGV_UIDS.get("D"), dps[0].tags().get(TAGV_UIDS.get("D"))); + assertArrayEquals(TAGV_UIDS.get("F"), dps[1].tags().get(TAGV_UIDS.get("D"))); + assertArrayEquals(TAGV_UIDS.get("G"), dps[2].tags().get(TAGV_UIDS.get("D"))); + } + + @Test + public void aPlusBResultsOffsetDefaultFill() throws Exception { + timeOffset(); + queryAB_Dstar(); + remapResults(); + + ExpressionIterator exp = new ExpressionIterator("ei", "a + b", + SetOperator.INTERSECTION, false, false); + exp.addResults("a", iterators.get("a")); + exp.addResults("b", iterators.get("b")); + + exp.compile(); + final ExpressionDataPoint[] dps = exp.values(); + assertEquals(2, dps.length); + validateMeta(dps, true); + + long ts = 1431561600000L; + long its = exp.nextTimestamp(); + exp.next(its); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(1, dps[0].toDouble(), 0.0001); + assertEquals(4, dps[1].toDouble(), 0.0001); + ts += 60000; + + its = exp.nextTimestamp(); + exp.next(its); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(2, dps[0].toDouble(), 0.0001); + assertEquals(5, dps[1].toDouble(), 0.0001); + ts += 60000; + + its = exp.nextTimestamp(); + exp.next(its); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(13, dps[0].toDouble(), 0.0001); + assertEquals(16, dps[1].toDouble(), 0.0001); + ts += 60000; + + its = exp.nextTimestamp(); + exp.next(its); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(14, dps[0].toDouble(), 0.0001); + assertEquals(17, dps[1].toDouble(), 0.0001); + + assertFalse(exp.hasNext()); + + for (int i = 0; i < dps.length; i++) { + assertEquals(2, dps[i].tags().size()); + assertTrue(dps[i].aggregatedTags().isEmpty()); + } + assertArrayEquals(TAGV_UIDS.get("D"), dps[0].tags().get(TAGV_UIDS.get("D"))); + assertArrayEquals(TAGV_UIDS.get("F"), dps[1].tags().get(TAGV_UIDS.get("D"))); + } + + @Test + public void aPlusBOneAggedOneTaggedUseQueryTagsWoutQueryTags() throws Exception { + oneAggedTheOtherTagged(); + queryAB_AggAll(); + remapResults(); + + ExpressionIterator exp = new ExpressionIterator("ei", "a + b", + SetOperator.INTERSECTION, true, false); + exp.addResults("a", iterators.get("a")); + exp.addResults("b", iterators.get("b")); + + exp.compile(); + final ExpressionDataPoint[] dps = exp.values(); + assertEquals(1, dps.length); + validateMeta(dps, true); + + long ts = 1431561600000L; + double value = 13; + long its = exp.nextTimestamp(); + while (exp.hasNext()) { + exp.next(its); + + assertEquals(ts, dps[0].timestamp()); + assertEquals(value, dps[0].toDouble(), 0.0001); + + value += 3; + ts += 60000; + its = exp.nextTimestamp(); + } + + assertEquals(2, dps[0].tags().size()); + assertEquals(2, dps[0].aggregatedTags().size()); + assertTrue(dps[0].aggregatedTags().contains(TAGV_UIDS.get("D"))); + assertTrue(dps[0].aggregatedTags().contains(TAGV_UIDS.get("E"))); + // TODO - make sure the tags are empty once the expression data does it's + // thing + //assertTrue(dps[0].tags().isEmpty()); + } + + @Test + public void singleNestedExpression() throws Exception { + oneExtraSameE(); + queryAB_Dstar(); + remapResults(); + + ExpressionIterator ei = new ExpressionIterator("ei", "a + b", + SetOperator.INTERSECTION, false, false); + ei.addResults("a", iterators.get("a")); + ei.addResults("b", iterators.get("b")); + ei.compile(); + + ExpressionIterator exp = new ExpressionIterator("ei", "x * 2", + SetOperator.INTERSECTION, false, false); + exp.addResults("x", ei); + + exp.compile(); + final ExpressionDataPoint[] dps = exp.values(); + assertEquals(2, dps.length); + validateMeta(dps, true); + + long ts = 1431561600000L; + double[] values = new double[] { 24, 36 }; + long its = exp.nextTimestamp(); + while (exp.hasNext()) { + exp.next(its); + + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(values[0], dps[0].toDouble(), 0.0001); + assertEquals(values[1], dps[1].toDouble(), 0.0001); + + values[0] += 4; + values[1] += 4; + ts += 60000; + its = exp.nextTimestamp(); + } + + for (int i = 0; i < dps.length; i++) { + assertEquals(2, dps[i].tags().size()); + assertTrue(dps[i].aggregatedTags().isEmpty()); + } + assertArrayEquals(TAGV_UIDS.get("D"), dps[0].tags().get(TAGV_UIDS.get("D"))); + assertArrayEquals(TAGV_UIDS.get("F"), dps[1].tags().get(TAGV_UIDS.get("D"))); + } + + @Test + public void doubleNestedExpression() throws Exception { + oneExtraSameE(); + queryAB_Dstar(); + remapResults(); + + ExpressionIterator e1 = new ExpressionIterator("e1", "a + b", + SetOperator.INTERSECTION, false, false); + e1.addResults("a", iterators.get("a")); + e1.addResults("b", iterators.get("b")); + e1.compile(); + + ExpressionIterator e2 = new ExpressionIterator("e2", "e1 * 2", + SetOperator.INTERSECTION, false, false); + e2.addResults("e1", e1); + e2.compile(); + + ExpressionIterator e3 = new ExpressionIterator("e3", "e2 * 2", + SetOperator.INTERSECTION, false, false); + e3.addResults("e2", e2); + + e3.compile(); + final ExpressionDataPoint[] dps = e3.values(); + assertEquals(2, dps.length); + validateMeta(dps, true); + + long ts = 1431561600000L; + double[] values = new double[] { 48, 72 }; + long its = e3.nextTimestamp(); + while (e3.hasNext()) { + e3.next(its); + + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(values[0], dps[0].toDouble(), 0.0001); + assertEquals(values[1], dps[1].toDouble(), 0.0001); + + values[0] += 8; + values[1] += 8; + ts += 60000; + its = e3.nextTimestamp(); + } + + for (int i = 0; i < dps.length; i++) { + assertEquals(2, dps[i].tags().size()); + assertTrue(dps[i].aggregatedTags().isEmpty()); + } + assertArrayEquals(TAGV_UIDS.get("D"), dps[0].tags().get(TAGV_UIDS.get("D"))); + assertArrayEquals(TAGV_UIDS.get("F"), dps[1].tags().get(TAGV_UIDS.get("D"))); + } + + @Test (expected = IllegalDataException.class) + public void noIntersectionFound() throws Exception { + threeDifE(); + queryAB_Dstar(); + remapResults(); + + ExpressionIterator exp = new ExpressionIterator("ei", "a + b", + SetOperator.INTERSECTION, false, false); + exp.addResults("a", iterators.get("a")); + exp.addResults("b", iterators.get("b")); + + exp.compile(); + } + + @Test (expected = IllegalArgumentException.class) + public void addResultsMissingId() throws Exception { + oneExtraSameE(); + queryAB_Dstar(); + + ExpressionIterator exp = new ExpressionIterator("ei", "a + b + c", + SetOperator.INTERSECTION, false, false); + exp.addResults("a", iterators.get("a")); + } + + @Test (expected = IllegalArgumentException.class) + public void addResultsMissingSubQuery() throws Exception { + oneExtraSameE(); + queryAB_Dstar(); + + ExpressionIterator exp = new ExpressionIterator("ei", "a + b + c", + SetOperator.INTERSECTION, false, false); + exp.addResults("a", iterators.get("a")); + } + + @Test (expected = IllegalArgumentException.class) + public void addResultsMissingResults() throws Exception { + oneExtraSameE(); + queryAB_Dstar(); + + ExpressionIterator exp = new ExpressionIterator("ei", "a + b + c", + SetOperator.INTERSECTION, false, false); + exp.addResults("a", iterators.get("a")); + } + + @Test + public void unionOneExtraSeries() throws Exception { + oneExtraSameE(); + queryAB_Dstar(); + remapResults(); + + ExpressionIterator exp = new ExpressionIterator("ei", "a + b", + SetOperator.UNION, false, false); + exp.addResults("a", iterators.get("a")); + exp.addResults("b", iterators.get("b")); + + exp.compile(); + final ExpressionDataPoint[] dps = exp.values(); + assertEquals(3, dps.length); + validateMeta(dps, true); + + long ts = 1431561600000L; + double[] values = new double[] { 12, 18, 17 }; + long its = exp.nextTimestamp(); + while (exp.hasNext()) { + exp.next(its); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(ts, dps[2].timestamp()); + assertEquals(values[0], dps[0].toDouble(), 0.0001); + assertEquals(values[1], dps[1].toDouble(), 0.0001); + assertEquals(values[2], dps[2].toDouble(), 0.0001); + + values[0] += 2; + values[1] += 2; + values[2] += 1; + ts += 60000; + its = exp.nextTimestamp(); + } + + for (int i = 0; i < dps.length; i++) { + assertEquals(2, dps[i].tags().size()); + assertTrue(dps[i].aggregatedTags().isEmpty()); + } + assertArrayEquals(TAGV_UIDS.get("D"), dps[0].tags().get(TAGV_UIDS.get("D"))); + assertArrayEquals(TAGV_UIDS.get("F"), dps[1].tags().get(TAGV_UIDS.get("D"))); + } + + @Test + public void unionOffset() throws Exception { + timeOffset(); + queryAB_Dstar(); + remapResults(); + + ExpressionIterator exp = new ExpressionIterator("ei", "a + b", + SetOperator.UNION, false, false); + exp.addResults("a", iterators.get("a")); + exp.addResults("b", iterators.get("b")); + + exp.compile(); + final ExpressionDataPoint[] dps = exp.values(); + assertEquals(2, dps.length); + validateMeta(dps, true); + + long ts = 1431561600000L; + long its = exp.nextTimestamp(); + exp.next(its); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(1, dps[0].toDouble(), 0.0001); + assertEquals(4, dps[1].toDouble(), 0.0001); + ts += 60000; + + its = exp.nextTimestamp(); + exp.next(its); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(2, dps[0].toDouble(), 0.0001); + assertEquals(5, dps[1].toDouble(), 0.0001); + ts += 60000; + + its = exp.nextTimestamp(); + exp.next(its); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(13, dps[0].toDouble(), 0.0001); + assertEquals(16, dps[1].toDouble(), 0.0001); + ts += 60000; + + its = exp.nextTimestamp(); + exp.next(its); + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(14, dps[0].toDouble(), 0.0001); + assertEquals(17, dps[1].toDouble(), 0.0001); + + assertFalse(exp.hasNext()); + + for (int i = 0; i < dps.length; i++) { + assertEquals(2, dps[i].tags().size()); + assertTrue(dps[i].aggregatedTags().isEmpty()); + } + assertArrayEquals(TAGV_UIDS.get("D"), dps[0].tags().get(TAGV_UIDS.get("D"))); + assertArrayEquals(TAGV_UIDS.get("F"), dps[1].tags().get(TAGV_UIDS.get("D"))); + } + + @Test + public void unionNoIntersection() throws Exception { + threeDifE(); + queryAB_Dstar(); + remapResults(); + + ExpressionIterator exp = new ExpressionIterator("ei", "a + b", + SetOperator.UNION, false, false); + exp.addResults("a", iterators.get("a")); + exp.addResults("b", iterators.get("b")); + + exp.compile(); + final ExpressionDataPoint[] dps = exp.values(); + assertEquals(6, dps.length); + validateMeta(dps, false); + + long ts = 1431561600000L; + double[] values = new double[] { 1, 11, 4, 14, 7, 17 }; + long its = exp.nextTimestamp(); + while (exp.hasNext()) { + exp.next(its); + for (int i = 0; i < values.length; i++) { + assertEquals(ts, dps[i].timestamp()); + assertEquals(values[i], dps[i].toDouble(), 0.0001); + ++values[i]; + } + + ts += 60000; + its = exp.nextTimestamp(); + } + } + + @Test + public void scratch() throws Exception { + threeDifE(); + queryAB_Dstar(); + remapResults(); + + ExpressionIterator exp = new ExpressionIterator("ei", "a + b", + SetOperator.UNION, false, false); + exp.addResults("a", iterators.get("a")); + exp.addResults("b", iterators.get("b")); + + exp.compile(); + final ExpressionDataPoint[] dps = exp.values(); + assertEquals(6, dps.length); + //validateMeta(dps, true); + + long ts = 1431561600000L; + double[] values = new double[] { 1, 11, 4, 14, 7, 17 }; + long its = exp.nextTimestamp(); + while (exp.hasNext()) { + exp.next(its); + for (int i = 0; i < dps.length; i++) { + System.out.println(dps[i].timestamp() + " " + dps[i].toDouble()); + } + + for (int i = 0; i < values.length; i++) { + assertEquals(ts, dps[i].timestamp()); + assertEquals(values[i], dps[i].toDouble(), 0.0001); + ++values[i]; + } + + ts += 60000; + its = exp.nextTimestamp(); + } + validateMeta(dps, false); + } + + /** + * Makes sure the series contain both metrics + * @param dps The results to validate + * @param common_e The common e + */ + private void validateMeta(final ExpressionDataPoint[] dps, + final boolean common_e) { + for (int i = 0; i < dps.length; i++) { + // TODO - change this guy to a byteset :( Since it's a bloody list we + // can't do a "contains" because it checks for the address of the byte + // arrays + boolean found = false; + for (final byte[] metric : dps[i].metricUIDs()) { + if (Bytes.memcmp(TAGV_UIDS.get("A"), metric) == 0) { + found = true; + } else if (Bytes.memcmp(TAGV_UIDS.get("B"), metric) == 0) { + found = true; + break; + } + } + if (!found) { + fail("Missing a metric"); + } + + if (common_e) { + assertArrayEquals(TAGV_UIDS.get("E"), dps[i].tags().get(TAGV_UIDS.get("E"))); + } + } + } + + private void remapResults() { + iterators.clear(); + iterators.put("a", new TimeSyncedIterator("a", + query.getQueries().get(0).getFilterTagKs(), results.get("0").getValue())); + iterators.put("b", new TimeSyncedIterator("b", + query.getQueries().get(1).getFilterTagKs(), results.get("1").getValue())); + } +} diff --git a/third_party/include.mk b/third_party/include.mk index 3a25a241a9..dc2f22d20b 100644 --- a/third_party/include.mk +++ b/third_party/include.mk @@ -24,6 +24,8 @@ include third_party/hamcrest/include.mk include third_party/jackson/include.mk include third_party/javacc/include.mk include third_party/javassist/include.mk +include third_party/jexl/include.mk +include third_party/jgrapht/include.mk include third_party/junit/include.mk include third_party/logback/include.mk include third_party/mockito/include.mk diff --git a/third_party/jexl/commons-jexl-2.1.1.jar.md5 b/third_party/jexl/commons-jexl-2.1.1.jar.md5 new file mode 100644 index 0000000000..866f0e175a --- /dev/null +++ b/third_party/jexl/commons-jexl-2.1.1.jar.md5 @@ -0,0 +1 @@ +4ad8f5c161dd3a50e190334555675db9 diff --git a/third_party/jexl/commons-logging-1.1.1.jar.md5 b/third_party/jexl/commons-logging-1.1.1.jar.md5 new file mode 100644 index 0000000000..00979c8fe9 --- /dev/null +++ b/third_party/jexl/commons-logging-1.1.1.jar.md5 @@ -0,0 +1 @@ +ed448347fc0104034aa14c8189bf37de diff --git a/third_party/jexl/include.mk b/third_party/jexl/include.mk new file mode 100644 index 0000000000..b78ce1e8ee --- /dev/null +++ b/third_party/jexl/include.mk @@ -0,0 +1,33 @@ +# Copyright (C) 2015 The OpenTSDB Authors. +# +# This library is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 2.1 of the License, or +# (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . + +JEXL_VERSION := 2.1.1 +JEXL := third_party/jexl/commons-jexl-$(JEXL_VERSION).jar +JEXL_BASE_URL := http://central.maven.org/maven2/org/apache/commons/commons-jexl/$(JEXL_VERSION) + +$(JEXL): $(JEXL).md5 + set dummy "$(JEXL_BASE_URL)" "$(JEXL)"; shift; $(FETCH_DEPENDENCY) + +THIRD_PARTY += $(JEXL) + +# In here as Jexl depends on it and no one else (for now, I hope) +COMMONS_LOGGING_VERSION := 1.1.1 +COMMONS_LOGGING := third_party/jexl/commons-logging-$(COMMONS_LOGGING_VERSION).jar +COMMONS_LOGGING_BASE_URL := http://central.maven.org/maven2/commons-logging/commons-logging/$(COMMONS_LOGGING_VERSION) + +$(COMMONS_LOGGING): $(COMMONS_LOGGING).md5 + set dummy "$(COMMONS_LOGGING_BASE_URL)" "$(COMMONS_LOGGING)"; shift; $(FETCH_DEPENDENCY) + +THIRD_PARTY += $(COMMONS_LOGGING) \ No newline at end of file diff --git a/third_party/jgrapht/include.mk b/third_party/jgrapht/include.mk new file mode 100644 index 0000000000..11647e3bcc --- /dev/null +++ b/third_party/jgrapht/include.mk @@ -0,0 +1,23 @@ +# Copyright (C) 2015 The OpenTSDB Authors. +# +# This library is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 2.1 of the License, or +# (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . + +JGRAPHT_VERSION := 0.9.1 +JGRAPHT := third_party/jgrapht/jgrapht-core-$(JGRAPHT_VERSION).jar +JGRAPHT_BASE_URL := http://central.maven.org/maven2/org/jgrapht/jgrapht-core/$(JGRAPHT_VERSION) + +$(JGRAPHT): $(JGRAPHT).md5 + set dummy "$(JGRAPHT_BASE_URL)" "$(JGRAPHT)"; shift; $(FETCH_DEPENDENCY) + +THIRD_PARTY += $(JGRAPHT) diff --git a/third_party/jgrapht/jgrapht-core-0.9.1.jar.md5 b/third_party/jgrapht/jgrapht-core-0.9.1.jar.md5 new file mode 100644 index 0000000000..a0089aa304 --- /dev/null +++ b/third_party/jgrapht/jgrapht-core-0.9.1.jar.md5 @@ -0,0 +1 @@ +86e15da146c96430aef3e1de36df52c8 From a4d47dc8b8221d442289edb3575622667744e33a Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Mon, 9 Nov 2015 11:23:01 -0800 Subject: [PATCH 069/233] Fix a bug where an HBase exception wasn't propagated to the end user at query time. Signed-off-by: Chris Larsen --- src/core/TsdbQuery.java | 16 +++++++++++++++- test/core/TestTsdbQuery.java | 22 ++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/core/TsdbQuery.java b/src/core/TsdbQuery.java index cef3748f15..7b060a44ed 100644 --- a/src/core/TsdbQuery.java +++ b/src/core/TsdbQuery.java @@ -566,6 +566,20 @@ final class ScannerCB implements Callback skips = new HashSet(); private final Set keepers = new HashSet(); + + /** Error callback that will capture an exception from AsyncHBase and store + * it so we can bubble it up to the caller. + */ + class ErrorCB implements Callback { + @Override + public Object call(final Exception e) throws Exception { + LOG.error("Scanner " + scanner + " threw an exception", e); + scanner.close(); + results.callback(e); + return null; + } + } + /** * Starts the scanner and is called recursively to fetch the next set of * rows from the scanner. @@ -574,7 +588,7 @@ final class ScannerCB implements Callback Date: Mon, 9 Nov 2015 11:27:12 -0800 Subject: [PATCH 070/233] Add the QueryExecutor TSD class (WIP) for handling expressions via the /query/exp endpoint. Signed-off-by: Chris Larsen --- Makefile.am | 2 + src/tsd/QueryExecutor.java | 877 ++++++++++++++++++++++++++++++++ src/tsd/QueryRpc.java | 18 + test/tsd/TestQueryExecutor.java | 607 ++++++++++++++++++++++ 4 files changed, 1504 insertions(+) create mode 100644 src/tsd/QueryExecutor.java create mode 100644 test/tsd/TestQueryExecutor.java diff --git a/Makefile.am b/Makefile.am index 113cb60954..edcaececad 100644 --- a/Makefile.am +++ b/Makefile.am @@ -149,6 +149,7 @@ tsdb_SRC := \ src/tsd/LogsRpc.java \ src/tsd/PipelineFactory.java \ src/tsd/PutDataPointRpc.java \ + src/tsd/QueryExecutor.java \ src/tsd/QueryRpc.java \ src/tsd/RpcHandler.java \ src/tsd/RpcPlugin.java \ @@ -316,6 +317,7 @@ test_SRC := \ test/tsd/TestHttpQuery.java \ test/tsd/TestHttpRpcPluginQuery.java \ test/tsd/TestPutRpc.java \ + test/tsd/TestQueryExecutor.java \ test/tsd/TestQueryRpc.java \ test/tsd/TestQueryRpcLastDataPoint.java \ test/tsd/TestRpcHandler.java \ diff --git a/src/tsd/QueryExecutor.java b/src/tsd/QueryExecutor.java new file mode 100644 index 0000000000..9a5683f01b --- /dev/null +++ b/src/tsd/QueryExecutor.java @@ -0,0 +1,877 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2010-2012 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.tsd; + +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBufferOutputStream; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.handler.codec.http.HttpResponseStatus; +import org.jgrapht.experimental.dag.DirectedAcyclicGraph; +import org.jgrapht.experimental.dag.DirectedAcyclicGraph.CycleFoundException; +import org.jgrapht.graph.DefaultEdge; +import org.jgrapht.traverse.TopologicalOrderIterator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.stumbleupon.async.Callback; +import com.stumbleupon.async.Deferred; +import com.stumbleupon.async.DeferredGroupException; + +import net.opentsdb.core.DataPoint; +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.QueryException; +import net.opentsdb.core.TSDB; +import net.opentsdb.core.TSQuery; +import net.opentsdb.core.TSSubQuery; +import net.opentsdb.core.Tags; +import net.opentsdb.query.expression.ExpressionDataPoint; +import net.opentsdb.query.expression.ExpressionIterator; +import net.opentsdb.query.expression.NumericFillPolicy; +import net.opentsdb.query.expression.TimeSyncedIterator; +import net.opentsdb.query.expression.VariableIterator.SetOperator; +import net.opentsdb.query.pojo.Expression; +import net.opentsdb.query.pojo.Filter; +import net.opentsdb.query.pojo.Metric; +import net.opentsdb.query.pojo.Output; +import net.opentsdb.query.pojo.Query; +import net.opentsdb.query.pojo.Timespan; +import net.opentsdb.stats.QueryStats; +import net.opentsdb.uid.UniqueId.UniqueIdType; +import net.opentsdb.utils.DateTime; +import net.opentsdb.utils.JSON; + +/** + * TEMP class for handling V2 queries with expression support. So far we ONLY + * support expressions and this will be pipelined better. For now it's functioning + * fairly well. + * + * So far this sucker allows for expressions and nested expressions with the + * ability to determine the output. If no output fields are specified, all + * expressions are dumped to the output. If one or more outputs are given then + * only those outputs will be emitted. + * + * TODO + * - handle/add output flags to determine whats emitted + * - allow for queries only, no expressions + * - possibly other set operations + * - time over time queries + * - skip querying for data that isn't going to be emitted + */ +public class QueryExecutor { + private static final Logger LOG = LoggerFactory.getLogger(QueryExecutor.class); + + /** The TSDB to which we belong (and will use for fetching data) */ + private final TSDB tsdb; + + /** The user's query */ + private final Query query; + + /** TEMP A v1 TSQuery that we use for fetching the data from HBase */ + private final TSQuery ts_query; + + /** A map of the sub queries to their Metric ids */ + private final Map sub_queries; + + /** A map of the sub query results to their Metric ids */ + private final Map sub_query_results; + + /** A map of expression iterators to their IDs */ + private final Map expressions; + + /** A map of Metric fill policies to the metric IDs */ + private final Map fills; + + /** The HTTP query from the user */ + private HttpQuery http_query; + + /** + * Default Ctor that constructs a TSQuery and TSSubQueries from the new + * Query POJO class. + * @param tsdb The TSDB to which we belong + * @param query The raw query to parse and use for output + * @throws IllegalArgumentException if we were unable to parse the Query into + * a TSQuery. + */ + public QueryExecutor(final TSDB tsdb, final Query query) { + this.tsdb = tsdb; + this.query = query; + + // if metrics is null, this is a bad query + sub_queries = new HashMap(query.getMetrics().size()); + sub_query_results = new HashMap( + query.getMetrics().size()); + + if (query.getExpressions() != null) { + expressions = new HashMap( + query.getExpressions().size()); + } else { + expressions = null; + } + + final Timespan timespan = query.getTime(); + + // compile the ts_query + ts_query = new TSQuery(); + ts_query.setStart(timespan.getStart()); + ts_query.setTimezone(timespan.getTimezone()); + + if (timespan.getEnd() != null && !timespan.getEnd().isEmpty()) { + ts_query.setEnd(timespan.getEnd()); + } + + fills = new HashMap(query.getMetrics().size()); + for (final Metric mq : query.getMetrics()) { + if (mq.getFillPolicy() != null) { + fills.put(mq.getId(), mq.getFillPolicy()); + } + final TSSubQuery sub = new TSSubQuery(); + sub_queries.put(mq.getId(), sub); + + sub.setMetric(mq.getMetric()); + + if (timespan.getDownsampler() != null) { + sub.setDownsample(timespan.getDownsampler().getInterval() + "-" + + timespan.getDownsampler().getAggregator()); + } + + // filters + if (mq.getFilter() != null && !mq.getFilter().isEmpty()) { + Filter filters = null; + if (query.getFilters() == null || query.getFilters().isEmpty()) { + throw new IllegalArgumentException("No filter defined: " + mq.getFilter()); + } + for (final Filter filter : query.getFilters()) { + if (filter.getId().equals(mq.getFilter())) { + filters = filter; + break; + } + } + sub.setRate(timespan.isRate()); + sub.setFilters(filters.getTags()); + sub.setAggregator( + mq.getAggregator() != null ? mq.getAggregator() : timespan.getAggregator()); + } + } + + final ArrayList subs = + new ArrayList(sub_queries.values()); + ts_query.setQueries(subs); + + // setup expressions + for (final Expression expression : query.getExpressions()) { + // TODO - flags + + // TODO - get a default from the configs + final SetOperator operator = expression.getJoin() != null ? + expression.getJoin().getOperator() : SetOperator.UNION; + final boolean qts = expression.getJoin() == null ? false : expression.getJoin().getUseQueryTags(); + final boolean ats = expression.getJoin() == null ? true : expression.getJoin().getIncludeAggTags(); + final ExpressionIterator iterator = + new ExpressionIterator(expression.getId(), expression.getExpr(), + operator, qts, ats); + if (expression.getFillPolicy() != null) { + iterator.setFillPolicy(expression.getFillPolicy()); + } + expressions.put(expression.getId(), iterator); + + } + + ts_query.validateAndSetQuery(); + } + + /** + * Execute the RPC and serialize the response + * @param query The HTTP query to parse and and return results to + */ + public void execute(final HttpQuery query) { + http_query = query; + final QueryStats query_stats = + new QueryStats(query.getRemoteAddress(), ts_query); + ts_query.setQueryStats(query_stats); + + final long start = DateTime.currentTimeMillis(); + + /** + * Sends the serialized results to the caller. This should be the very + * last callback executed. + */ + class CompleteCB implements Callback { + @Override + public Object call(final ChannelBuffer cb) throws Exception { + query.sendReply(cb); + return null; + } + } + + /** + * After all of the queries have run and we have data (or not) then we + * need to compile the iterators. + * This class could probably be improved: + * First we iterate over the results AND for each result, iterate over + * the expressions, giving a time synced iterator to each expression that + * needs the result set. + * THEN we iterate over the expressions again and build a DAG to determine + * if any of the expressions require the output of an expression. If so + * then we add the expressions to the proper parent and compile them in + * order. + * After all of that we're ready to start serializing and iterating + * over the results. + */ + class QueriesCB implements Callback> { + public Object call(final ArrayList query_results) + throws Exception { + + query_stats.setTimeStorage(DateTime.currentTimeMillis() - start); + for (int i = 0; i < query_results.size(); i++) { + final TSSubQuery sub = ts_query.getQueries().get(i); + + Iterator> it = sub_queries.entrySet().iterator(); + while (it.hasNext()) { + final Entry entry = it.next(); + if (entry.getValue().equals(sub)) { + sub_query_results.put(entry.getKey(), query_results.get(i)); + for (final ExpressionIterator ei : expressions.values()) { + if (ei.getVariableNames().contains(entry.getKey())) { + final TimeSyncedIterator tsi = new TimeSyncedIterator( + entry.getKey(), sub.getFilterTagKs(), + query_results.get(i)); + final NumericFillPolicy fill = fills.get(entry.getKey()); + if (fill != null) { + tsi.setFillPolicy(fill); + } + ei.addResults(entry.getKey(), tsi); + LOG.debug("Added results for " + entry.getKey() + + " to " + ei.getId()); + } + } + } + } + } + + // handle nested expressions + DirectedAcyclicGraph graph = null; + for (final Entry eii : expressions.entrySet()) { + for (final String var : eii.getValue().getVariableNames()) { + final ExpressionIterator ei = expressions.get(var); + if (ei != null) { + // TODO - really ought to calculate this earlier + if (eii.getKey().equals(var)) { + throw new IllegalArgumentException( + "Self referencing expression found: " + eii.getKey()); + } + LOG.debug("Nested expression detected. " + eii.getKey() + + " depends on " + var); + + if (graph == null) { + graph = new DirectedAcyclicGraph(DefaultEdge.class); + } + if (!graph.containsVertex(eii.getKey())) { + graph.addVertex(eii.getKey()); + } + if (!graph.containsVertex(var)) { + graph.addVertex(var); + } + try { + graph.addDagEdge(eii.getKey(), var); + } catch (CycleFoundException cfe) { + throw new IllegalArgumentException("Circular reference found: " + + eii.getKey(), cfe); + } + } + } + } + + // compile all of the expressions + final long intersect_start = DateTime.currentTimeMillis(); + if (graph != null) { + final ExpressionIterator[] compile_stack = + new ExpressionIterator[expressions.size()]; + final TopologicalOrderIterator it = + new TopologicalOrderIterator(graph); + int i = 0; + while (it.hasNext()) { + compile_stack[i++] = expressions.get(it.next()); + } + for (int x = compile_stack.length - 1; x >= 0; x--) { + // look for and add expressions + for (final String var : compile_stack[x].getVariableNames()) { + ExpressionIterator source = expressions.get(var); + if (source != null) { + compile_stack[x].addResults(var, source.getCopy()); + LOG.debug("Adding expression " + source.getId() + " to " + + compile_stack[x].getId()); + } + } + + compile_stack[x].compile(); + LOG.debug("Successfully compiled " + compile_stack[x]); + } + } else { + for (final ExpressionIterator ei : expressions.values()) { + ei.compile(); + LOG.debug("Successfully compiled " + ei); + } + } + LOG.debug("Finished compilations in " + + (DateTime.currentTimeMillis() - intersect_start) + " ms"); + + return serialize().addCallback(new CompleteCB()).addErrback(new ErrorCB()); + } + } + + /** + * Callback executed after we have resolved the metric, tag names and tag + * values to their respective UIDs. This callback then runs the actual + * queries and fetches their results. + */ + class BuildCB implements Callback, net.opentsdb.core.Query[]> { + @Override + public Deferred call(final net.opentsdb.core.Query[] queries) { + final ArrayList> deferreds = + new ArrayList>(queries.length); + + for (final net.opentsdb.core.Query query : queries) { + deferreds.add(query.runAsync()); + } + return Deferred.groupInOrder(deferreds).addCallback(new QueriesCB()) + .addErrback(new ErrorCB()); + } + } + + // TODO - only run the ones that will be involved in an output. Folks WILL + // ask for stuff they don't need.... *sigh* + ts_query.buildQueriesAsync(tsdb).addCallback(new BuildCB()) + .addErrback(new ErrorCB()); + } + + /** + * Writes the results to a ChannelBuffer to return to the caller. This will + * iterate over all of the outputs and drop in meta data where appropriate. + * @throws Exception if something went pear shaped + */ + private Deferred serialize() throws Exception { + final long start = System.currentTimeMillis(); + // buffers and an array list to stored the deferreds + final ChannelBuffer response = ChannelBuffers.dynamicBuffer(); + final OutputStream output_stream = new ChannelBufferOutputStream(response); + + final JsonGenerator json = JSON.getFactory().createGenerator(output_stream); + json.writeStartObject(); + json.writeFieldName("outputs"); + json.writeStartArray(); + + // We want the serializer to execute serially so we need to create a callback + // chain so that when one DPsResolver is finished, it triggers the next to + // start serializing. + final Deferred cb_chain = new Deferred(); + + // default to the expressions if there, or fall back to the metrics + final List outputs; + if (query.getOutputs() == null || query.getOutputs().isEmpty()) { + if (query.getExpressions() != null && !query.getExpressions().isEmpty()) { + outputs = new ArrayList(query.getExpressions().size()); + for (final Expression exp : query.getExpressions()) { + outputs.add(Output.Builder().setId(exp.getId()).build()); + } + } else if (query.getMetrics() != null && !query.getMetrics().isEmpty()) { + outputs = new ArrayList(query.getMetrics().size()); + for (final Metric metric : query.getMetrics()) { + outputs.add(Output.Builder().setId(metric.getId()).build()); + } + } else { + throw new IllegalArgumentException( + "How did we get here?? No metrics or expressions??"); + } + } else { + outputs = query.getOutputs(); + } + + for (final Output output : outputs) { + if (expressions != null) { + final ExpressionIterator it = expressions.get(output.getId()); + if (it != null) { + cb_chain.addCallback(new SerializeExpressionIterator(tsdb, json, + output, it, ts_query)); + continue; + } + } + + if (query.getMetrics() != null && !query.getMetrics().isEmpty()) { + final TSSubQuery sub = sub_queries.get(output.getId()); + if (sub != null) { + final TimeSyncedIterator it = new TimeSyncedIterator(output.getId(), + sub.getFilterTagKs(), sub_query_results.get(output.getId())); + cb_chain.addCallback(new SerializeSubIterator(tsdb, json, output, it)); + continue; + } + } else { + LOG.warn("Couldn't find a variable matching: " + output.getId() + + " in query " + query); + } + } + + /** Final callback to close out the JSON array and return our results */ + class FinalCB implements Callback { + public ChannelBuffer call(final Object obj) + throws Exception { + json.writeEndArray(); + + ts_query.getQueryStats().setTimeSerialization( + DateTime.currentTimeMillis() - start); + ts_query.getQueryStats().markComplete(); + + // dump overall stats as an extra object in the array + if (true) { + final QueryStats stats = ts_query.getQueryStats(); + json.writeFieldName("statsSummary"); + json.writeStartObject(); + //json.writeStringField("hostname", TSDB.getHostname()); + //json.writeNumberField("runningQueries", stats.getNumRunningQueries()); + json.writeNumberField("datapoints", stats.getAggregatedSize()); + json.writeNumberField("rawDatapoints", stats.getSize()); + //json.writeNumberField("rowsFetched", stats.getRowsFetched()); + json.writeNumberField("aggregationTime", stats.getTimeAggregation()); + json.writeNumberField("serializationTime", stats.getTimeSerialization()); + json.writeNumberField("storageTime", stats.getTimeStorage()); + json.writeNumberField("timeTotal", + ((double)stats.getTimeTotal() / (double)1000000)); + json.writeEndObject(); + } + + // dump the original query + if (true) { + json.writeFieldName("query"); + json.writeObject(QueryExecutor.this.query); + } + // IMPORTANT Make sure the close the JSON array and the generator + json.writeEndObject(); + json.close(); + return response; + } + } + + // trigger the callback chain here + cb_chain.callback(null); + return cb_chain.addCallback(new FinalCB()); + } + + /** This has to be attached to callbacks or we may never respond to clients */ + class ErrorCB implements Callback { + public Object call(final Exception e) throws Exception { + try { + LOG.error("Query exception: ", e); + if (e instanceof DeferredGroupException) { + Throwable ex = e.getCause(); + while (ex != null && ex instanceof DeferredGroupException) { + ex = ex.getCause(); + } + if (ex != null) { + LOG.error("Unexpected exception: ", ex); + // TODO - find a better way to determine the real error + QueryExecutor.this.ts_query.getQueryStats() + .markComplete(HttpResponseStatus.BAD_REQUEST, ex); + QueryExecutor.this.http_query.badRequest(new BadRequestException(ex)); + } else { + LOG.error("The deferred group exception didn't have a cause???"); + QueryExecutor.this.ts_query.getQueryStats() + .markComplete(HttpResponseStatus.INTERNAL_SERVER_ERROR, ex); + QueryExecutor.this.http_query.badRequest(new BadRequestException(e)); + } + } else if (e.getClass() == QueryException.class) { + QueryExecutor.this.ts_query.getQueryStats() + .markComplete(HttpResponseStatus.REQUEST_TIMEOUT, e); + QueryExecutor.this.http_query.badRequest(new BadRequestException((QueryException)e)); + } else { + QueryExecutor.this.ts_query.getQueryStats() + .markComplete(HttpResponseStatus.INTERNAL_SERVER_ERROR, e); + QueryExecutor.this.http_query.badRequest(new BadRequestException(e)); + } + return null; + } catch (RuntimeException ex) { + LOG.error("Exception thrown during exception handling", ex); + QueryExecutor.this.ts_query.getQueryStats() + .markComplete(HttpResponseStatus.INTERNAL_SERVER_ERROR, ex); + QueryExecutor.this.http_query.sendReply(HttpResponseStatus.INTERNAL_SERVER_ERROR, + ex.getMessage().getBytes()); + return null; + } + } + } + + /** + * Handles serializing the output of an expression iterator + */ + private class SerializeExpressionIterator + implements Callback, Object> { + final TSDB tsdb; + final JsonGenerator json; + final Output output; + final ExpressionIterator iterator; + final ExpressionDataPoint[] dps; + final TSQuery query; + + // WARNING: Make sure to write an endObject() before triggering this guy + final Deferred completed; + + /** + * The default ctor to setup the serializer + * @param tsdb The TSDB to use for name resolution + * @param json The JSON generator to write to + * @param output The Output spec associated with this expression + * @param iterator The iterator to run through + * @param query The original TSQuery + */ + public SerializeExpressionIterator(final TSDB tsdb, final JsonGenerator json, + final Output output, final ExpressionIterator iterator, final TSQuery query) { + this.tsdb = tsdb; + this.json = json; + this.output = output; + this.iterator = iterator; + this.query = query; + dps = iterator.values(); + completed = new Deferred(); + } + + /** Super simple closer that tells the upstream chain we're done with this */ + class MetaCB implements Callback { + @Override + public Object call(final Object ignored) throws Exception { + completed.callback(null); + return completed; + } + } + + @Override + public Deferred call(final Object ignored) throws Exception { + //result set opening + json.writeStartObject(); + + json.writeStringField("id", output.getId()); + if (output.getAlias() != null) { + json.writeStringField("alias", output.getAlias()); + } + json.writeFieldName("dps"); + json.writeStartArray(); + + long first_ts = Long.MIN_VALUE; + long last_ts = 0; + long count = 0; + long ts = iterator.nextTimestamp(); + long qs = query.startTime(); + long qe = query.endTime(); + while (iterator.hasNext()) { + iterator.next(ts); + + long timestamp = dps[0].timestamp(); + if (timestamp >= qs && timestamp <= qe) { + json.writeStartArray(); + if (dps.length > 0) { + json.writeNumber(timestamp); + if (first_ts == Long.MIN_VALUE) { + first_ts = timestamp; + } else { + last_ts = timestamp; + } + ++count; + } + for (int i = 0; i < dps.length; i++) { + json.writeNumber(dps[i].toDouble()); + } + + json.writeEndArray(); + } + ts = iterator.nextTimestamp(); + } + json.writeEndArray(); + + // data points meta + json.writeFieldName("dpsMeta"); + json.writeStartObject(); + json.writeNumberField("firstTimestamp", first_ts < 0 ? 0 : first_ts); + json.writeNumberField("lastTimestamp", last_ts); + json.writeNumberField("setCount", count); + json.writeNumberField("series", dps.length); + json.writeEndObject(); + + // resolve meta LAST since we may not even need it + if (dps.length > 0) { + final MetaSerializer meta_serializer = + new MetaSerializer(tsdb, json, iterator.values()); + meta_serializer.call(null).addCallback(new MetaCB()) + .addErrback(QueryExecutor.this.new ErrorCB()); + } else { + // done, not dumping any more info + json.writeEndObject(); + //json.writeEndArray(); + completed.callback(null); + } + + return completed; + } + + } + + /** + * Serializes a raw, non expression result set. + */ + private class SerializeSubIterator implements + Callback, Object> { + final TSDB tsdb; + final JsonGenerator json; + final Output output; + final TimeSyncedIterator iterator; + + // WARNING: Make sure to write an endObject() before triggering this guy + final Deferred completed; + + public SerializeSubIterator(final TSDB tsdb, final JsonGenerator json, + final Output output, final TimeSyncedIterator iterator) { + this.tsdb = tsdb; + this.json = json; + this.output = output; + this.iterator = iterator; + completed = new Deferred(); + } + + class MetaCB implements Callback { + @Override + public Object call(final Object ignored) throws Exception { + completed.callback(null); + return completed; + } + } + + @Override + public Deferred call(final Object ignored) throws Exception { + //result set opening + json.writeStartObject(); + + json.writeStringField("id", output.getId()); + if (output.getAlias() != null) { + json.writeStringField("alias", output.getAlias()); + } + json.writeFieldName("dps"); + json.writeStartArray(); + + final long first_ts = iterator.nextTimestamp(); + long ts = first_ts; + long last_ts = 0; + long count = 0; + final DataPoint[] dps = iterator.values(); + while (iterator.hasNext()) { + iterator.next(ts); + json.writeStartArray(); + + if (dps.length > 0) { + json.writeNumber(dps[0].timestamp()); + last_ts = dps[0].timestamp(); + ++count; + } + for (int i = 0; i < dps.length; i++) { + json.writeNumber(dps[i].toDouble()); + } + + json.writeEndArray(); + ts = iterator.nextTimestamp(); + } + json.writeEndArray(); + + // data points meta + json.writeFieldName("dpsMeta"); + json.writeStartObject(); + json.writeNumberField("firstTimestamp", first_ts); + json.writeNumberField("lastTimestamp", last_ts); + json.writeNumberField("setCount", count); + json.writeNumberField("series", dps.length); + json.writeEndObject(); + + // resolve meta LAST since we may not even need it + if (dps.length > 0) { + final DataPoints[] odps = iterator.getDataPoints(); + final ExpressionDataPoint[] edps = new ExpressionDataPoint[dps.length]; + for (int i = 0; i < dps.length; i++) { + edps[i] = new ExpressionDataPoint(odps[i]); + } + final MetaSerializer meta_serializer = + new MetaSerializer(tsdb, json, edps); + meta_serializer.call(null).addCallback(new MetaCB()); + } else { + // done, not dumping any more info + json.writeEndObject(); + completed.callback(null); + } + + return completed; + } + + } + + /** + * Handles resolving metrics, tags, aggregated tags and other meta data + * associated with a result set. + */ + private class MetaSerializer implements Callback, Object> { + final TSDB tsdb; + final JsonGenerator json; + final ExpressionDataPoint[] dps; + final List metrics; + final Map[] tags; + final List[] agg_tags; + + final Deferred completed; + + @SuppressWarnings("unchecked") + public MetaSerializer(final TSDB tsdb, final JsonGenerator json, + final ExpressionDataPoint[] dps) { + this.tsdb = tsdb; + this.json = json; + this.dps = dps; + completed = new Deferred(); + metrics = new ArrayList(); + tags = new Map[dps.length]; + agg_tags = new List[dps.length]; + } + + class MetricsCB implements Callback> { + @Override + public Object call(final ArrayList names) throws Exception { + metrics.addAll(names); + Collections.sort(metrics); + return null; + } + } + + class AggTagsCB implements Callback> { + final int index; + public AggTagsCB(final int index) { + this.index = index; + } + @Override + public Object call(final ArrayList tags) throws Exception { + agg_tags[index] = tags; + return null; + } + } + + class TagsCB implements Callback> { + final int index; + public TagsCB(final int index) { + this.index = index; + } + @Override + public Object call(final Map tags) throws Exception { + MetaSerializer.this.tags[index] = tags; + return null; + } + } + + class MetaCB implements Callback> { + @Override + public Object call(final ArrayList ignored) throws Exception { + json.writeFieldName("meta"); + json.writeStartArray(); + + // first field is the timestamp + json.writeStartObject(); + json.writeNumberField("index", 0); + json.writeFieldName("metrics"); + json.writeStartArray(); + json.writeString("timestamp"); + json.writeEndArray(); + json.writeEndObject(); + + for (int i = 0; i < dps.length; i++) { + json.writeStartObject(); + + json.writeNumberField("index", i + 1); + json.writeFieldName("metrics"); + json.writeObject(metrics); + + json.writeFieldName("commonTags"); + if (tags[i] == null) { + json.writeObject(Collections.emptyMap()); + } else { + json.writeObject(tags[i]); + } + + json.writeFieldName("aggregatedTags"); + if (agg_tags[i] == null) { + json.writeObject(Collections.emptyList()); + } else { + json.writeObject(agg_tags[i]); + } + + // TODO restore when we can calculate size efficiently + //json.writeNumberField("dps", dps[i].size()); + //json.writeNumberField("rawDps", dps[i].rawSize()); + + json.writeEndObject(); + } + + json.writeEndArray(); + + // all done with this series of results + json.writeEndObject(); + completed.callback(null); + return null; + } + } + + @Override + public Deferred call(final Object ignored) throws Exception { + final List> deferreds = + new ArrayList>(); + + final List> metric_deferreds = + new ArrayList>(dps[0].metricUIDs().size()); + + for (final byte[] uid : dps[0].metricUIDs()) { + metric_deferreds.add(tsdb.getUidName(UniqueIdType.METRIC, uid)); + } + + deferreds.add(Deferred.group(metric_deferreds) + .addCallback(new MetricsCB())); + + for (int i = 0; i < dps.length; i++) { + if (dps[i].aggregatedTags().size() > 0) { + final List> agg_deferreds = + new ArrayList>(dps[i].aggregatedTags().size()); + for (final byte[] uid : dps[i].aggregatedTags()) { + agg_deferreds.add(tsdb.getUidName(UniqueIdType.TAGK, uid)); + } + deferreds.add(Deferred.group(agg_deferreds) + .addCallback(new AggTagsCB(i))); + } + + deferreds.add(Tags.getTagsAsync(tsdb, dps[i].tags()) + .addCallback(new TagsCB(i))); + } + + Deferred.groupInOrder(deferreds).addCallback(new MetaCB()) + .addErrback(QueryExecutor.this.new ErrorCB()); + return completed; + } + + } + +} diff --git a/src/tsd/QueryRpc.java b/src/tsd/QueryRpc.java index 4b5ab81a7b..554e586b9a 100644 --- a/src/tsd/QueryRpc.java +++ b/src/tsd/QueryRpc.java @@ -49,6 +49,7 @@ import net.opentsdb.uid.NoSuchUniqueName; import net.opentsdb.uid.UniqueId; import net.opentsdb.utils.DateTime; +import net.opentsdb.utils.JSON; /** * Handles queries for timeseries datapoints. Each request is parsed into a @@ -94,6 +95,9 @@ public void execute(final TSDB tsdb, final HttpQuery query) handleLastDataPointQuery(tsdb, query); } else if (endpoint.toLowerCase().equals("gexp")){ handleQuery(tsdb, query, true); + } else if (endpoint.toLowerCase().equals("exp")) { + handleExpressionQuery(tsdb, query); + return; } else { handleQuery(tsdb, query, false); } @@ -278,6 +282,20 @@ public Object call(final List annotations) throws Exception { } } + /** + * Handles an expression query + * @param tsdb The TSDB to which we belong + * @param query The HTTP query to parse/respond + * @since 2.3 + */ + private void handleExpressionQuery(final TSDB tsdb, final HttpQuery query) { + final net.opentsdb.query.pojo.Query v2_query = + JSON.parseToObject(query.getContent(), net.opentsdb.query.pojo.Query.class); + v2_query.validate(); + final QueryExecutor executor = new QueryExecutor(tsdb, v2_query); + executor.execute(query); + } + /** * Processes a last data point query * @param tsdb The TSDB to which we belong diff --git a/test/tsd/TestQueryExecutor.java b/test/tsd/TestQueryExecutor.java new file mode 100644 index 0000000000..a1a021156b --- /dev/null +++ b/test/tsd/TestQueryExecutor.java @@ -0,0 +1,607 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2010-2012 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.tsd; + +import static org.junit.Assert.assertTrue; + +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import net.opentsdb.core.TSDB; +import net.opentsdb.core.TSQuery; +import net.opentsdb.query.expression.BaseTimeSyncedIteratorTest; +import net.opentsdb.query.expression.VariableIterator.SetOperator; +import net.opentsdb.query.filter.TagVFilter; +import net.opentsdb.query.pojo.Expression; +import net.opentsdb.query.pojo.Filter; +import net.opentsdb.query.pojo.Join; +import net.opentsdb.query.pojo.Metric; +import net.opentsdb.query.pojo.Output; +import net.opentsdb.query.pojo.Query; +import net.opentsdb.query.pojo.Timespan; +import net.opentsdb.storage.MockBase; +import net.opentsdb.utils.Config; +import net.opentsdb.utils.DateTime; +import net.opentsdb.utils.JSON; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import com.stumbleupon.async.Deferred; +import com.stumbleupon.async.DeferredGroupException; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({TSDB.class, Config.class, HttpQuery.class, + Deferred.class, TSQuery.class, DateTime.class, DeferredGroupException.class }) +public class TestQueryExecutor extends BaseTimeSyncedIteratorTest { + + private Timespan time; + private List tags; + private List filters; + private List metrics; + private List expressions; + private List outputs; + private Join intersection; + + @Before + public void setup() { + intersection = Join.Builder().setOperator(SetOperator.INTERSECTION).build(); + time = Timespan.Builder().setStart("1431561600") + .setAggregator("sum").build(); + + tags = Arrays.asList(new TagVFilter.Builder().setFilter("*").setGroupBy(true) + .setTagk("D").setType("wildcard").build()); + + filters = Arrays.asList(Filter.Builder().setId("f1") + .setTags(tags).build()); + final Metric metric1 = Metric.Builder().setMetric("A").setId("a") + .setFilter("f1").build(); + final Metric metric2 = Metric.Builder().setMetric("B").setId("b") + .setFilter("f1").build(); + metrics = Arrays.asList(metric1, metric2); + expressions = Arrays.asList(Expression.Builder().setId("e") + .setExpression("a + b").setJoin(intersection).build()); + outputs = Arrays.asList(Output.Builder().setId("e").setAlias("A plus B") + .build()); + } + + @Test + public void oneExpressionWithOutputAlias() throws Exception { + oneExtraSameE(); + final String json = JSON.serializeToString(getDefaultQueryBuilder().build()); + final QueryRpc rpc = new QueryRpc(); + final HttpQuery query = NettyMocks.postQuery(tsdb, + "/api/query/exp", json); + + rpc.execute(tsdb, query); + final String response = + query.response().getContent().toString(Charset.forName("UTF-8")); + assertTrue(response.contains("\"alias\":\"A plus B\"")); + assertTrue(response.contains("\"dps\":[[1431561600000,12.0,18.0]")); + assertTrue(response.contains("[1431561660000,14.0,20.0]")); + assertTrue(response.contains("[1431561720000,16.0,22.0]")); + assertTrue(response.contains("\"firstTimestamp\":1431561600000")); + assertTrue(response.contains("\"index\":1")); + assertTrue(response.contains("\"metrics\":[\"A\",\"B\"]")); + assertTrue(response.contains("\"index\":2")); + } + + @Test + public void oneExpressionDefaultOutput() throws Exception { + oneExtraSameE(); + final Query q = Query.Builder().setExpressions(expressions) + .setFilters(filters).setMetrics(metrics).setName("q1") + .setTime(time).build(); + final String json = JSON.serializeToString(q); + final QueryRpc rpc = new QueryRpc(); + final HttpQuery query = NettyMocks.postQuery(tsdb, + "/api/query/exp", json); + + rpc.execute(tsdb, query); + final String response = + query.response().getContent().toString(Charset.forName("UTF-8")); + assertTrue(response.contains("\"id\":\"e\"")); + assertTrue(response.contains("\"dps\":[[1431561600000,12.0,18.0]")); + assertTrue(response.contains("[1431561660000,14.0,20.0]")); + assertTrue(response.contains("[1431561720000,16.0,22.0]")); + assertTrue(response.contains("\"firstTimestamp\":1431561600000")); + assertTrue(response.contains("\"index\":1")); + assertTrue(response.contains("\"metrics\":[\"A\",\"B\"]")); + assertTrue(response.contains("\"index\":2")); + // TODO - more asserts once we settle on names + } + + @Test + public void oneExpressionOutputAndBAlso() throws Exception { + oneExtraSameE(); + + outputs = new ArrayList(3); + outputs.add(Output.Builder().setId("e").setAlias("A plus B").build()); + outputs.add(Output.Builder().setId("a").build()); + outputs.add(Output.Builder().setId("b").build()); + + final String json = JSON.serializeToString(getDefaultQueryBuilder().build()); + + final QueryRpc rpc = new QueryRpc(); + final HttpQuery query = NettyMocks.postQuery(tsdb, + "/api/query/exp", json); + + rpc.execute(tsdb, query); + final String response = + query.response().getContent().toString(Charset.forName("UTF-8")); + assertTrue(response.contains("\"alias\":\"A plus B\"")); + assertTrue(response.contains("\"dps\":[[1431561600000,12.0,18.0]")); + assertTrue(response.contains("[1431561660000,14.0,20.0]")); + assertTrue(response.contains("[1431561720000,16.0,22.0]")); + assertTrue(response.contains("\"firstTimestamp\":1431561600000")); + assertTrue(response.contains("\"index\":1")); + assertTrue(response.contains("\"metrics\":[\"A\",\"B\"]")); + assertTrue(response.contains("\"index\":2")); + + assertTrue(response.contains("\"id\":\"a\"")); + assertTrue(response.contains("\"dps\":[[1431561600000,1.0,4.0]")); + assertTrue(response.contains("\"metrics\":[\"A\"]")); + assertTrue(response.contains("\"id\":\"b\"")); + assertTrue(response.contains("\"dps\":[[1431561600000,11.0,14.0,17.0]")); + assertTrue(response.contains("\"metrics\":[\"B\"]")); + } + + @Test + public void oneExpressionDefaultFill() throws Exception { + threeSameEGaps(); + String json = JSON.serializeToString(getDefaultQueryBuilder()); + final QueryRpc rpc = new QueryRpc(); + final HttpQuery query = NettyMocks.postQuery(tsdb, + "/api/query/exp", json); + query.getQueryBaseRoute(); // to the correct serializer + + rpc.execute(tsdb, query); + final String response = + query.response().getContent().toString(Charset.forName("UTF-8")); + assertTrue(response.contains("\"alias\":\"A plus B\"")); + assertTrue(response.contains("\"dps\":[[1431561600000,1.0,4.0,0.0]")); + assertTrue(response.contains("[1431561660000,0.0,20.0,8.0]")); + assertTrue(response.contains("[1431561720000,16.0,0.0,28.0]")); + assertTrue(response.contains("\"firstTimestamp\":1431561600000")); + assertTrue(response.contains("\"index\":1")); + assertTrue(response.contains("\"metrics\":[\"A\",\"B\"]")); + assertTrue(response.contains("\"index\":2")); + assertTrue(response.contains("\"index\":3")); + } + + @Test + public void twoExpressionsDefaultOutput() throws Exception { + oneExtraSameE(); + expressions = Arrays.asList( + Expression.Builder().setId("e").setExpression("a + b") + .setJoin(intersection).build(), + Expression.Builder().setId("e2").setExpression("a * b") + .setJoin(intersection).build()); + + final Query q = Query.Builder().setExpressions(expressions) + .setFilters(filters).setMetrics(metrics).setName("q1") + .setTime(time).build(); + final String json = JSON.serializeToString(q); + final QueryRpc rpc = new QueryRpc(); + final HttpQuery query = NettyMocks.postQuery(tsdb, + "/api/query/exp", json); + + rpc.execute(tsdb, query); + final String response = + query.response().getContent().toString(Charset.forName("UTF-8")); + assertTrue(response.contains("\"id\":\"e\"")); + assertTrue(response.contains("\"dps\":[[1431561600000,12.0,18.0]")); + assertTrue(response.contains("[1431561660000,14.0,20.0]")); + assertTrue(response.contains("[1431561720000,16.0,22.0]")); + assertTrue(response.contains("\"firstTimestamp\":1431561600000")); + assertTrue(response.contains("\"index\":1")); + assertTrue(response.contains("\"metrics\":[\"A\",\"B\"]")); + assertTrue(response.contains("\"index\":2")); + assertTrue(response.contains("\"id\":\"e2\"")); + assertTrue(response.contains("\"dps\":[[1431561600000,11.0,56.0]")); + assertTrue(response.contains("[1431561660000,24.0,75.0]")); + assertTrue(response.contains("[1431561720000,39.0,96.0]")); + } + + @Test + public void twoExpressionsOneWithoutResultsDefaultOutput() throws Exception { + oneExtraSameE(); + final Metric metric1 = Metric.Builder().setMetric("A").setId("a") + .setFilter("f1").setAggregator("sum").build(); + final Metric metric2 = Metric.Builder().setMetric("B").setId("b") + .setFilter("f1").setAggregator("sum").build(); + final Metric metric3 = Metric.Builder().setMetric("D").setId("d") + .setFilter("f1").setAggregator("sum").build(); + final Metric metric4 = Metric.Builder().setMetric("F").setId("f") + .setFilter("f1").setAggregator("sum").build(); + metrics = Arrays.asList(metric1, metric2, metric3, metric4); + + expressions = Arrays.asList( + Expression.Builder().setId("e").setExpression("a + b") + .setJoin(intersection).build(), + Expression.Builder().setId("x").setExpression("d + f") + .setJoin(intersection).build()); + + final Query q = Query.Builder().setExpressions(expressions) + .setFilters(filters).setMetrics(metrics).setName("q1") + .setTime(time).build(); + String json = JSON.serializeToString(q); + final QueryRpc rpc = new QueryRpc(); + final HttpQuery query = NettyMocks.postQuery(tsdb, + "/api/query/exp", json); + query.getQueryBaseRoute(); // to the correct serializer + + rpc.execute(tsdb, query); + final String response = + query.response().getContent().toString(Charset.forName("UTF-8")); + assertTrue(response.contains("\"id\":\"e\"")); + assertTrue(response.contains("\"dps\":[[1431561600000,12.0,18.0]")); + assertTrue(response.contains("[1431561660000,14.0,20.0]")); + assertTrue(response.contains("[1431561720000,16.0,22.0]")); + assertTrue(response.contains("\"firstTimestamp\":1431561600000")); + assertTrue(response.contains("\"index\":1")); + assertTrue(response.contains("\"metrics\":[\"A\",\"B\"]")); + assertTrue(response.contains("\"index\":2")); + assertTrue(response.contains("\"id\":\"x\"")); + assertTrue(response.contains("\"dps\":[]")); + assertTrue(response.contains("\"firstTimestamp\":0")); + assertTrue(response.contains("\"series\":0")); + } + + @Test + public void multiExpressionsOneOutput() throws Exception { + oneExtraSameE(); + expressions = Arrays.asList( + Expression.Builder().setId("e").setExpression("a + b").setJoin(intersection).build(), + Expression.Builder().setId("e2").setExpression("e * 2").setJoin(intersection).build(), + Expression.Builder().setId("e3").setExpression("e * 2").setJoin(intersection).build(), + Expression.Builder().setId("e4").setExpression("e2 + e3").setJoin(intersection).build()); + + final String json = JSON.serializeToString(getDefaultQueryBuilder()); + final QueryRpc rpc = new QueryRpc(); + final HttpQuery query = NettyMocks.postQuery(tsdb, + "/api/query/exp", json); + query.getQueryBaseRoute(); // to the correct serializer + + rpc.execute(tsdb, query); + final String response = + query.response().getContent().toString(Charset.forName("UTF-8")); + assertTrue(response.contains("[1431561660000,14.0,20.0]")); + assertTrue(response.contains("[1431561720000,16.0,22.0]")); + assertTrue(response.contains("\"firstTimestamp\":1431561600000")); + assertTrue(response.contains("\"index\":1")); + assertTrue(response.contains("\"metrics\":[\"A\",\"B\"]")); + assertTrue(response.contains("\"index\":2")); + } + + @Test + public void nestedExpressionsOneLevelDefaultOutput() throws Exception { + oneExtraSameE(); + expressions = Arrays.asList( + Expression.Builder().setId("e").setExpression("a + b") + .setJoin(intersection).build(), + Expression.Builder().setId("e2").setExpression("e * 2") + .setJoin(intersection).build()); + + final Query q = Query.Builder().setExpressions(expressions) + .setFilters(filters).setMetrics(metrics).setName("q1") + .setTime(time).build(); + final String json = JSON.serializeToString(q); + final QueryRpc rpc = new QueryRpc(); + final HttpQuery query = NettyMocks.postQuery(tsdb, + "/api/query/exp", json); + + rpc.execute(tsdb, query); + final String response = + query.response().getContent().toString(Charset.forName("UTF-8")); + assertTrue(response.contains("\"id\":\"e\"")); + assertTrue(response.contains("\"dps\":[[1431561600000,12.0,18.0]")); + assertTrue(response.contains("[1431561660000,14.0,20.0]")); + assertTrue(response.contains("[1431561720000,16.0,22.0]")); + assertTrue(response.contains("\"firstTimestamp\":1431561600000")); + assertTrue(response.contains("\"index\":1")); + assertTrue(response.contains("\"metrics\":[\"A\",\"B\"]")); + assertTrue(response.contains("\"index\":2")); + assertTrue(response.contains("\"id\":\"e2\"")); + assertTrue(response.contains("\"dps\":[[1431561600000,24.0,36.0]")); + assertTrue(response.contains("[1431561660000,28.0,40.0]")); + assertTrue(response.contains("[1431561720000,32.0,44.0]")); + } + + @Test + public void nestedExpressionsTwoLevelsDefaultOutput() throws Exception { + oneExtraSameE(); + expressions = Arrays.asList( + Expression.Builder().setId("e").setExpression("a + b").setJoin(intersection).build(), + Expression.Builder().setId("e2").setExpression("e * 2").setJoin(intersection).build(), + Expression.Builder().setId("e3").setExpression("e * 2").setJoin(intersection).build(), + Expression.Builder().setId("e4").setExpression("e2 + e3").setJoin(intersection).build()); + + final Query q = Query.Builder().setExpressions(expressions) + .setFilters(filters).setMetrics(metrics).setName("q1") + .setTime(time).build(); + final String json = JSON.serializeToString(q); + final QueryRpc rpc = new QueryRpc(); + final HttpQuery query = NettyMocks.postQuery(tsdb, + "/api/query/exp", json); + + rpc.execute(tsdb, query); + final String response = + query.response().getContent().toString(Charset.forName("UTF-8")); + assertTrue(response.contains("\"id\":\"e\"")); + assertTrue(response.contains("\"dps\":[[1431561600000,12.0,18.0]")); + assertTrue(response.contains("[1431561660000,14.0,20.0]")); + assertTrue(response.contains("[1431561720000,16.0,22.0]")); + assertTrue(response.contains("\"firstTimestamp\":1431561600000")); + assertTrue(response.contains("\"index\":1")); + assertTrue(response.contains("\"metrics\":[\"A\",\"B\"]")); + assertTrue(response.contains("\"index\":2")); + assertTrue(response.contains("\"id\":\"e2\"")); + assertTrue(response.contains("\"dps\":[[1431561600000,24.0,36.0]")); + assertTrue(response.contains("[1431561660000,28.0,40.0]")); + assertTrue(response.contains("[1431561720000,32.0,44.0]")); + assertTrue(response.contains("\"id\":\"e3\"")); + assertTrue(response.contains("\"id\":\"e4\"")); + assertTrue(response.contains("\"dps\":[[1431561600000,48.0,72.0]")); + assertTrue(response.contains("[1431561660000,56.0,80.0]")); + assertTrue(response.contains("[1431561720000,64.0,88.0]")); + } + + @Test + public void nestedExpressionsTwoLevelsDefaultOutputOrdering() throws Exception { + oneExtraSameE(); + expressions = Arrays.asList( + Expression.Builder().setId("e2").setExpression("e * 2").setJoin(intersection).build(), + Expression.Builder().setId("e4").setExpression("e2 + e3").setJoin(intersection).build(), + Expression.Builder().setId("e3").setExpression("e * 2").setJoin(intersection).build(), + Expression.Builder().setId("e").setExpression("a + b").setJoin(intersection).build() + ); + + final Query q = Query.Builder().setExpressions(expressions) + .setFilters(filters).setMetrics(metrics).setName("q1") + .setTime(time).build(); + final String json = JSON.serializeToString(q); + final QueryRpc rpc = new QueryRpc(); + final HttpQuery query = NettyMocks.postQuery(tsdb, + "/api/query/exp", json); + + rpc.execute(tsdb, query); + final String response = + query.response().getContent().toString(Charset.forName("UTF-8")); + assertTrue(response.contains("\"id\":\"e\"")); + assertTrue(response.contains("\"dps\":[[1431561600000,12.0,18.0]")); + assertTrue(response.contains("[1431561660000,14.0,20.0]")); + assertTrue(response.contains("[1431561720000,16.0,22.0]")); + assertTrue(response.contains("\"firstTimestamp\":1431561600000")); + assertTrue(response.contains("\"index\":1")); + assertTrue(response.contains("\"metrics\":[\"A\",\"B\"]")); + assertTrue(response.contains("\"index\":2")); + assertTrue(response.contains("\"id\":\"e2\"")); + assertTrue(response.contains("\"dps\":[[1431561600000,24.0,36.0]")); + assertTrue(response.contains("[1431561660000,28.0,40.0]")); + assertTrue(response.contains("[1431561720000,32.0,44.0]")); + assertTrue(response.contains("\"id\":\"e3\"")); + assertTrue(response.contains("\"id\":\"e4\"")); + assertTrue(response.contains("\"dps\":[[1431561600000,48.0,72.0]")); + assertTrue(response.contains("[1431561660000,56.0,80.0]")); + assertTrue(response.contains("[1431561720000,64.0,88.0]")); + } + + @Test + public void emptyResultSet() throws Exception { + setDataPointStorage(); + String json = JSON.serializeToString(getDefaultQueryBuilder()); + final QueryRpc rpc = new QueryRpc(); + final HttpQuery query = NettyMocks.postQuery(tsdb, + "/api/query/exp", json); + query.getQueryBaseRoute(); // to the correct serializer + + rpc.execute(tsdb, query); + final String response = + query.response().getContent().toString(Charset.forName("UTF-8")); + assertTrue(response.contains("\"dps\":[]")); + assertTrue(response.contains("\"firstTimestamp\":0")); + assertTrue(response.contains("\"series\":0")); + } + + @Test + public void scannerException() throws Exception { + oneExtraSameE(); + storage.throwException(MockBase.stringToBytes( + "00000B5553E58000000D00000F00000E00000E"), + new RuntimeException("Boo!"), true); + final String json = JSON.serializeToString(getDefaultQueryBuilder().build()); + + final QueryRpc rpc = new QueryRpc(); + final HttpQuery query = NettyMocks.postQuery(tsdb, + "/api/query/exp", json); + query.getQueryBaseRoute(); // to the correct serializer + + rpc.execute(tsdb, query); + final String response = + query.response().getContent().toString(Charset.forName("UTF-8")); + assertTrue(response.contains("\"code\":400")); + assertTrue(response.contains("\"message\":\"Boo!\"")); + } + + @Test + public void nsunMetric() throws Exception { + oneExtraSameE(); + final Metric metric1 = Metric.Builder().setMetric("A").setId("a") + .setFilter("f1").setAggregator("sum").build(); + final Metric metric2 = Metric.Builder().setMetric(NSUN_METRIC).setId("b") + .setFilter("f1").setAggregator("sum").build(); + metrics = Arrays.asList(metric1, metric2); + final String json = JSON.serializeToString(getDefaultQueryBuilder().build()); + + final QueryRpc rpc = new QueryRpc(); + final HttpQuery query = NettyMocks.postQuery(tsdb, + "/api/query/exp", json); + query.getQueryBaseRoute(); // to the correct serializer + + rpc.execute(tsdb, query); + final String response = + query.response().getContent().toString(Charset.forName("UTF-8")); + assertTrue(response.contains("\"code\":400")); + assertTrue(response.contains("\"message\":\"No such name for '" + + NSUN_METRIC + "'")); + } + + @Test + public void selfReferencingExpression() throws Exception { + oneExtraSameE(); + expressions = Arrays.asList( + Expression.Builder().setId("e").setExpression("a + b").build(), + Expression.Builder().setId("e2").setExpression("e * 2").build(), + Expression.Builder().setId("e3").setExpression("e * 2").build(), + Expression.Builder().setId("e4").setExpression("e2 + e4").build()); + + final Query q = Query.Builder().setExpressions(expressions) + .setFilters(filters).setMetrics(metrics).setName("q1") + .setTime(time).build(); + final String json = JSON.serializeToString(q); + final QueryRpc rpc = new QueryRpc(); + final HttpQuery query = NettyMocks.postQuery(tsdb, + "/api/query/exp", json); + query.getQueryBaseRoute(); // to the correct serializer + + rpc.execute(tsdb, query); + final String response = + query.response().getContent().toString(Charset.forName("UTF-8")); + assertTrue(response.contains("\"code\":400")); + assertTrue(response.contains("\"message\":\"Self referencing")); + } + + @Test + public void circularReferenceExpression() throws Exception { + oneExtraSameE(); + expressions = Arrays.asList( + Expression.Builder().setId("e").setExpression("a + e4").build(), + Expression.Builder().setId("e2").setExpression("e * 2").build(), + Expression.Builder().setId("e3").setExpression("e * 2").build(), + Expression.Builder().setId("e4").setExpression("e2 + e3").build()); + + final Query q = Query.Builder().setExpressions(expressions) + .setFilters(filters).setMetrics(metrics).setName("q1") + .setTime(time).build(); + final String json = JSON.serializeToString(q); + final QueryRpc rpc = new QueryRpc(); + final HttpQuery query = NettyMocks.postQuery(tsdb, + "/api/query/exp", json); + query.getQueryBaseRoute(); // to the correct serializer + + rpc.execute(tsdb, query); + final String response = + query.response().getContent().toString(Charset.forName("UTF-8")); + assertTrue(response.contains("\"code\":400")); + assertTrue(response.contains("\"message\":\"Circular reference found:")); + } + + @Test + public void noIntersectionsFound() throws Exception { + threeDifE(); + + String json = JSON.serializeToString(getDefaultQueryBuilder()); + final QueryRpc rpc = new QueryRpc(); + final HttpQuery query = NettyMocks.postQuery(tsdb, + "/api/query/exp", json); + query.getQueryBaseRoute(); // to the correct serializer + + rpc.execute(tsdb, query); + final String response = + query.response().getContent().toString(Charset.forName("UTF-8")); + assertTrue(response.contains("\"code\":400")); + assertTrue(response.contains("\"message\":\"No intersections found")); + } + + @Test + public void noIntersectionsFoundNestedExpression() throws Exception { + oneExtraSameE(); + + final Metric metric1 = Metric.Builder().setMetric("A").setId("a") + .setFilter("f1").setAggregator("sum").build(); + final Metric metric2 = Metric.Builder().setMetric("B").setId("b") + .setFilter("f1").setAggregator("sum").build(); + final Metric metric3 = Metric.Builder().setMetric("D").setId("d") + .setFilter("f1").setAggregator("sum").build(); + metrics = Arrays.asList(metric1, metric2, metric3); + + expressions = Arrays.asList( + Expression.Builder().setId("e").setExpression("a + b").setJoin(intersection).build(), + Expression.Builder().setId("x").setExpression("d + e").setJoin(intersection).build()); + + final Query q = Query.Builder().setExpressions(expressions) + .setFilters(filters).setMetrics(metrics).setName("q1") + .setTime(time).build(); + String json = JSON.serializeToString(q); + final QueryRpc rpc = new QueryRpc(); + final HttpQuery query = NettyMocks.postQuery(tsdb, + "/api/query/exp", json); + query.getQueryBaseRoute(); // to the correct serializer + + rpc.execute(tsdb, query); + final String response = + query.response().getContent().toString(Charset.forName("UTF-8")); + assertTrue(response.contains("\"code\":400")); + assertTrue(response.contains("\"message\":\"No intersections found")); + } + + @Test + public void noIntersectionsFoundOneMetricEmpty() throws Exception { + oneExtraSameE(); + final Metric metric1 = Metric.Builder().setMetric("A").setId("a") + .setFilter("f1").setAggregator("sum").build(); + final Metric metric2 = Metric.Builder().setMetric("D").setId("b") + .setFilter("f1").setAggregator("sum").build(); + metrics = Arrays.asList(metric1, metric2); + String json = JSON.serializeToString(getDefaultQueryBuilder()); + final QueryRpc rpc = new QueryRpc(); + final HttpQuery query = NettyMocks.postQuery(tsdb, + "/api/query/exp", json); + query.getQueryBaseRoute(); // to the correct serializer + + rpc.execute(tsdb, query); + final String response = + query.response().getContent().toString(Charset.forName("UTF-8")); + assertTrue(response.contains("\"code\":400")); + assertTrue(response.contains("\"message\":\"No intersections found")); + } + + @Test + public void notEnoughMetrics() throws Exception { + oneExtraSameE(); + expressions = Arrays.asList( + Expression.Builder().setId("e").setExpression("a + b + c").build()); + String json = JSON.serializeToString(getDefaultQueryBuilder()); + final QueryRpc rpc = new QueryRpc(); + final HttpQuery query = NettyMocks.postQuery(tsdb, + "/api/query/exp", json); + query.getQueryBaseRoute(); // to the correct serializer + + rpc.execute(tsdb, query); + final String response = + query.response().getContent().toString(Charset.forName("UTF-8")); + assertTrue(response.contains("\"code\":400")); + assertTrue(response.contains("\"message\":\"Not enough query results")); + } + + protected Query.Builder getDefaultQueryBuilder() { + return Query.Builder().setExpressions(expressions).setFilters(filters) + .setMetrics(metrics).setName("q1").setTime(time).setOutputs(outputs); + } +} From 6d019ca24d46332f6ec0d1da1173d305aa5ec9fb Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Thu, 5 Nov 2015 18:55:04 -0800 Subject: [PATCH 071/233] Add an Tags.getTagsAsync() override that accepts a ByteMap. --- src/core/Tags.java | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/core/Tags.java b/src/core/Tags.java index 83ec687824..462196e152 100644 --- a/src/core/Tags.java +++ b/src/core/Tags.java @@ -462,6 +462,49 @@ public Map call(final ArrayList names) return Deferred.groupInOrder(deferreds).addCallback(new NameCB()); } + /** + * Returns the names mapped to tag key/value UIDs + * @param tsdb The TSDB instance to use for Unique ID lookups. + * @param tags The map of tag key to tag value pairs + * @return A map of tag names (keys), tag values (values). If the tags list + * was null or empty, the result will be an empty map + * @throws NoSuchUniqueId if the row key contained an invalid ID. + * @since 2.3 + */ + public static Deferred> getTagsAsync(final TSDB tsdb, + final ByteMap tags) { + if (tags == null || tags.isEmpty()) { + return Deferred.fromResult(Collections.emptyMap()); + } + + final ArrayList> deferreds = + new ArrayList>(); + + for (final Map.Entry pair : tags) { + deferreds.add(tsdb.tag_names.getNameAsync(pair.getKey())); + deferreds.add(tsdb.tag_values.getNameAsync(pair.getValue())); + } + + class NameCB implements Callback, ArrayList> { + public Map call(final ArrayList names) + throws Exception { + final HashMap result = new HashMap(); + String tagk = ""; + for (String name : names) { + if (tagk.isEmpty()) { + tagk = name; + } else { + result.put(tagk, name); + tagk = ""; + } + } + return result; + } + } + + return Deferred.groupInOrder(deferreds).addCallback(new NameCB()); + } + /** * Returns the tag key and value pairs as a byte map given a row key * @param row The row key to parse the UIDs from From b7d9a481854cc98055b9bc52eba920873d5c7595 Mon Sep 17 00:00:00 2001 From: louyl Date: Mon, 9 Nov 2015 16:53:31 +0800 Subject: [PATCH 072/233] Fix IncomingDataPoint toString, separate tags with space Signed-off-by: Chris Larsen --- src/core/IncomingDataPoint.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/IncomingDataPoint.java b/src/core/IncomingDataPoint.java index 0a7c70970d..dced8077ef 100644 --- a/src/core/IncomingDataPoint.java +++ b/src/core/IncomingDataPoint.java @@ -94,10 +94,10 @@ public String toString() { final StringBuilder buf = new StringBuilder(); buf.append("metric=").append(this.metric); buf.append(" ts=").append(this.timestamp); - buf.append(" value=").append(this.value).append(" "); + buf.append(" value=").append(this.value); if (this.tags != null) { for (Map.Entry entry : this.tags.entrySet()) { - buf.append(entry.getKey()).append("=").append(entry.getValue()); + buf.append(" ").append(entry.getKey()).append("=").append(entry.getValue()); } } return buf.toString(); From fd32bed6f17f24c987fcae9255f5750a84e280de Mon Sep 17 00:00:00 2001 From: Johannes Meixner Date: Mon, 9 Nov 2015 20:36:05 +0200 Subject: [PATCH 073/233] Fix staging and build on FreeBSD Make sure that the filesystem outside of DESTDIR isn't touched, hence set java.util.prefs.userRoot to $(HOME) and make sure etc/ config files go to $(sysconfdir)/etc/opentsdb instead of $(pkgdatadir)/etc/opentsdb. Signed-off-by: Chris Larsen --- Makefile.am | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile.am b/Makefile.am index edcaececad..d829bbfd13 100644 --- a/Makefile.am +++ b/Makefile.am @@ -441,7 +441,7 @@ printdeps: # This is kind of a hack, but I couldn't find a better way to adjust the paths # in the script before it gets installed... install-exec-hook: - script=tsdb; pkgdatadir='$(pkgdatadir)'; configdir='$(pkgdatadir)/etc/opentsdb'; \ + script=tsdb; pkgdatadir='$(pkgdatadir)'; configdir='$(sysconfigdir)/etc/opentsdb'; \ abs_srcdir=''; abs_builddir=''; $(edit_tsdb_script) cat tsdb.tmp >"$(DESTDIR)$(bindir)/tsdb" rm -f tsdb.tmp @@ -501,7 +501,7 @@ gwtc: .gwtc-stamp @$(mkdir_p) gwt { cd $(srcdir) && cat $(httpui_SRC); } | $(MD5) >"$@-t" cmp -s "$@" "$@-t" && exit 0; \ - $(JAVA) $(GWTC_JVM_ARGS) -cp $(GWT_CLASSPATH) com.google.gwt.dev.Compiler \ + $(JAVA) -Djava.util.prefs.userRoot=$(HOME) $(GWTC_JVM_ARGS) -cp $(GWT_CLASSPATH) com.google.gwt.dev.Compiler \ $(GWTC_ARGS) -war gwt tsd.QueryUi @mv "$@-t" "$@" From 755f0768a7e6aa004dbc6ea1dee012c7bbd9bcf9 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Mon, 9 Nov 2015 11:38:43 -0800 Subject: [PATCH 074/233] Fix a typo regarding TestTimeSpan.java class in the Makefile. Signed-off-by: Chris Larsen --- Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index d829bbfd13..b525dce11b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -291,7 +291,7 @@ test_SRC := \ test/query/pojo/TestMetric.java \ test/query/pojo/TestOutput.java \ test/query/pojo/TestQuery.java \ - test/query/pojo/TestTimespan.java \ + test/query/pojo/TestTimeSpan.java \ test/search/TestSearchPlugin.java \ test/search/TestSearchQuery.java \ test/search/TestTimeSeriesLookup.java \ From 65289cd7c621a6c21472c93a1fef6681f5be4951 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Wed, 11 Nov 2015 14:27:44 -0800 Subject: [PATCH 075/233] Make sure the JavaCC parser is compiled when creating the dist tarball or packages. Signed-off-by: Chris Larsen --- Makefile.am | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Makefile.am b/Makefile.am index b525dce11b..e1567acf92 100644 --- a/Makefile.am +++ b/Makefile.am @@ -387,14 +387,15 @@ httpui_SRC := \ httpui_DEPS = src/tsd/QueryUi.gwt.xml # TODO(CL) - There is likely a MUCH better way to compile and add the expression sources and jars. +expr_grammar = $(srcdir)/src/parser.jj expr_package = net/opentsdb/query/expression/parser -expr_src_dir = $(abs_builddir)/src/$(expr_package) +expr_src_dir = $(builddir)/src/$(expr_package) get_expr_classes = `classes=''; for f in $(packagedir)$(expr_package)/*.class; do classes="$$classes $$f"; done; echo $$classes;` #dist_pkgdata_DATA = src/logback.xml dist_static_DATA = src/tsd/static/favicon.ico -EXTRA_DIST = tsdb.in $(tsdb_SRC) $(test_SRC) \ +EXTRA_DIST = tsdb.in $(tsdb_SRC) $(test_SRC) $(expr_grammar) \ $(test_plugin_SRC) $(test_plugin_MF) $(test_plugin_SVCS:%=test/%) \ $(THIRD_PARTY) $(THIRD_PARTY:=.md5) \ $(httpui_SRC) $(httpui_DEPS) \ @@ -449,7 +450,7 @@ install-exec-hook: $(builddata_SRC): .git/HEAD $(tsdb_SRC) $(top_srcdir)/build-aux/gen_build_data.sh $(srcdir)/build-aux/gen_build_data.sh $(builddata_SRC) $(package).$(builddata_subpackage) $(PACKAGE_VERSION) -jar: runjavacc $(jar) .javac-unittests-stamp .gwtc-stamp +jar: $(jar) .javac-unittests-stamp .gwtc-stamp JAVA_COMPILE := $(JAVAC) $(AM_JAVACFLAGS) -d . @@ -483,9 +484,9 @@ $(tsdb_SRC): $(tsdb_DEPS) find_jar = test -f "$$jar" && echo "$$jar" || echo "$(srcdir)/$$jar" get_dep_classpath = `for jar in $(tsdb_DEPS); do $(find_jar); done | tr '\n' ':'` -.javac-stamp: $(tsdb_SRC) $(builddata_SRC) +.javac-stamp: $(tsdb_SRC) $(builddata_SRC) runjavacc @$(filter_src); cp=$(get_dep_classpath); \ - echo "DO THA COMPILE!!! $(JAVA_COMPILE) -cp $$cp $$src"; \ + echo "$(JAVA_COMPILE) -cp $$cp $$src"; \ $(JAVA_COMPILE) -cp $$cp $$src @touch "$@" @@ -725,7 +726,7 @@ $(JAVADOC_DIR)/index.html: $(tsdb_SRC) $? $(builddata_SRC) runjavacc: - $(JAVA) -cp $(JAVACC) javacc -OUTPUT_DIRECTORY:$(expr_src_dir) $(abs_srcdir)/src/parser.jj; echo PWD: `pwd`; + $(JAVA) -cp $(JAVACC) javacc -OUTPUT_DIRECTORY:$(expr_src_dir) $(expr_grammar); echo PWD: `pwd`; dist-hook: $(mkdir_p) $(distdir)/.git From 4d2fb7c44f1bb99c5cba8e097c5b31335f455804 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Wed, 11 Nov 2015 15:13:15 -0800 Subject: [PATCH 076/233] Rollback to fix the static file bug copying introduced in 8dcd77d --- Makefile.am | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile.am b/Makefile.am index e1567acf92..521e0d238e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -541,8 +541,8 @@ install-data-local: staticroot install-data-lib install-data-tools \ install-data-bin install-data-etc @$(NORMAL_INSTALL) test -z "$(staticdir)" || $(mkdir_p) "$(DESTDIR)$(staticdir)" - @set -e; pwd; ls -lFh; (cd "$(DEV_TSD_STATICROOT)"; \ - list=`find -L . ! -type d`); for p in $$list; do \ + @set -e; pwd; ls -lFh; cd "$(DEV_TSD_STATICROOT)"; \ + list=`find -L . ! -type d`; for p in $$list; do \ p=$${p#./}; \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ dstdir=`dirname "$(DESTDIR)$(staticdir)/$$p"`; \ From 9aa14a37700e7f9b5a7d7b50fdc51653f89c5889 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Fri, 13 Nov 2015 12:36:01 -0800 Subject: [PATCH 077/233] Add the EDPtoDPS class for converting from an ExpressionDataPoint to a DataPoints object. Signed-off-by: Chris Larsen --- Makefile.am | 1 + src/query/expression/EDPtoDPS.java | 246 +++++++++++++++++++++++++++++ 2 files changed, 247 insertions(+) create mode 100644 src/query/expression/EDPtoDPS.java diff --git a/Makefile.am b/Makefile.am index 521e0d238e..c68f7186be 100644 --- a/Makefile.am +++ b/Makefile.am @@ -75,6 +75,7 @@ tsdb_SRC := \ src/meta/UIDMeta.java \ src/query/QueryUtil.java \ src/query/expression/Absolute.java \ + src/query/expression/EDPtoDPS.java \ src/query/expression/Expression.java \ src/query/expression/ExpressionDataPoint.java \ src/query/expression/ExpressionFactory.java \ diff --git a/src/query/expression/EDPtoDPS.java b/src/query/expression/EDPtoDPS.java new file mode 100644 index 0000000000..4c17ff70e6 --- /dev/null +++ b/src/query/expression/EDPtoDPS.java @@ -0,0 +1,246 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.hbase.async.Bytes.ByteMap; + +import com.stumbleupon.async.Callback; +import com.stumbleupon.async.Deferred; + +import net.opentsdb.core.DataPoint; +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.MutableDataPoint; +import net.opentsdb.core.SeekableView; +import net.opentsdb.core.TSDB; +import net.opentsdb.core.Tags; +import net.opentsdb.meta.Annotation; +import net.opentsdb.uid.UniqueId.UniqueIdType; +import net.opentsdb.utils.ByteSet; + +/** + * An ugly temporary class for converting from an expression datapoint to a + * standard data point for serialization in the default query format. + */ +public class EDPtoDPS implements DataPoints { + /** The TSDB used for UID to name lookups */ + private final TSDB tsdb; + + /** The index of this data point in the iterator */ + private final int index; + + /** The iterator that contains the results for this data point */ + private final ExpressionIterator iterator; + + /** The list of data points from the iterator from which we read */ + private final ExpressionDataPoint[] edps; + + /** + * Default ctor + * @param tsdb The TSDB used for UID to name lookups + * @param index The index of this data point in the iterator + * @param iterator The iterator that contains the results for this data point + */ + public EDPtoDPS(final TSDB tsdb, final int index, + final ExpressionIterator iterator) { + this.tsdb = tsdb; + this.index = index; + this.iterator = iterator; + edps = iterator.values(); + } + + @Override + public String metricName() { + try { + return metricNameAsync().joinUninterruptibly(); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException("Should never be here", e); + } + } + + @Override + public Deferred metricNameAsync() { + if (edps[index].metricUIDs() == null) { + throw new IllegalStateException("Iterator UID was null for index " + + index + " and iterator " + iterator); + } + final byte[] uid = edps[index].metricUIDs().iterator().next(); + return tsdb.getUidName(UniqueIdType.METRIC, uid); + } + + @Override + public byte[] metricUID() { + if (edps[index].metricUIDs() == null) { + throw new IllegalStateException("Iterator UID was null for index " + + index + " and iterator " + iterator); + } + return edps[index].metricUIDs().iterator().next(); + } + + @Override + public Map getTags() { + try { + return getTagsAsync().joinUninterruptibly(); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException("Should never be here", e); + } + } + + @Override + public Deferred> getTagsAsync() { + return Tags.getTagsAsync(tsdb, edps[index].tags()); + } + + @Override + public ByteMap getTagUids() { + return edps[index].tags(); + } + + @Override + public List getAggregatedTags() { + try { + return getAggregatedTagsAsync().joinUninterruptibly(); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException("Should never be here", e); + } + } + + @Override + public Deferred> getAggregatedTagsAsync() { + final ByteSet tagks = edps[index].aggregatedTags(); + final List aggregated_tags = new ArrayList(tagks.size()); + + final List> names = + new ArrayList>(tagks.size()); + for (final byte[] tagk : tagks) { + names.add(tsdb.getUidName(UniqueIdType.TAGK, tagk)); + } + + /** Adds the names to the aggregated_tags list */ + final class ResolveCB implements Callback, ArrayList> { + @Override + public List call(final ArrayList names) throws Exception { + for (final String name : names) { + aggregated_tags.add(name); + } + return aggregated_tags; + } + } + + return Deferred.group(names).addCallback(new ResolveCB()); + } + + @Override + public List getAggregatedTagUids() { + final List agg_tags = new ArrayList( + edps[index].aggregatedTags()); + return agg_tags; + } + + @Override + public List getTSUIDs() { + // TODO Fix it up + return Collections.emptyList(); + } + + @Override + public List getAnnotations() { + // TODO Fix it up + return Collections.emptyList(); + } + + @Override + public int size() { + // TODO Estimate + return -1; + } + + @Override + public int aggregatedSize() { + // TODO Estimate + return -1; + } + + @Override + public SeekableView iterator() { + return new Iterator(); + } + + @Override + public long timestamp(int i) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isInteger(int i) { + throw new UnsupportedOperationException(); + } + + @Override + public long longValue(int i) { + throw new UnsupportedOperationException(); + } + + @Override + public double doubleValue(int i) { + throw new UnsupportedOperationException(); + } + + @Override + public int getQueryIndex() { + // TODO Fix it up + return 0; + } + + /** + * Simple class that fills the local data point while iterating through the + * expression data points at the proper index. + */ + private class Iterator implements SeekableView { + /** A data pont to mutate as we iterate */ + final MutableDataPoint dp = new MutableDataPoint(); + + @Override + public boolean hasNext() { + return iterator.hasNext(index); + } + + @Override + public DataPoint next() { + iterator.next(index); + dp.reset(edps[index].timestamp(), edps[index].toDouble()); + return dp; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public void seek(long timestamp) { + throw new UnsupportedOperationException(); + } + + } +} From ef3e911c548814100634eba452171ef0eaffa651 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Fri, 13 Nov 2015 12:38:32 -0800 Subject: [PATCH 078/233] Add the DivideSeries function. Also modify the ExpressionFactory to load some functions after the TSDB object has been initialized and call that load method in the TSDB ctor. Signed-off-by: Chris Larsen --- Makefile.am | 4 +- src/core/TSDB.java | 5 + src/query/expression/DivideSeries.java | 86 +++++++++ src/query/expression/ExpressionFactory.java | 11 ++ test/query/expression/TestDivideSeries.java | 197 ++++++++++++++++++++ 5 files changed, 302 insertions(+), 1 deletion(-) create mode 100644 src/query/expression/DivideSeries.java create mode 100644 test/query/expression/TestDivideSeries.java diff --git a/Makefile.am b/Makefile.am index c68f7186be..58e6c3c405 100644 --- a/Makefile.am +++ b/Makefile.am @@ -75,7 +75,8 @@ tsdb_SRC := \ src/meta/UIDMeta.java \ src/query/QueryUtil.java \ src/query/expression/Absolute.java \ - src/query/expression/EDPtoDPS.java \ + src/query/expression/Expression.java \ + src/query/expression/DivideSeries.java \ src/query/expression/Expression.java \ src/query/expression/ExpressionDataPoint.java \ src/query/expression/ExpressionFactory.java \ @@ -265,6 +266,7 @@ test_SRC := \ test/meta/TestUIDMeta.java \ test/query/expression/BaseTimeSyncedIteratorTest.java \ test/query/expression/TestAbsolute.java \ + test/query/expression/TestDivideSeries.java \ test/query/expression/TestExpressionFactory.java \ test/query/expression/TestExpressionIterator.java \ test/query/expression/TestExpressionReader.java \ diff --git a/src/core/TSDB.java b/src/core/TSDB.java index 64cf75c779..900f038128 100644 --- a/src/core/TSDB.java +++ b/src/core/TSDB.java @@ -54,6 +54,7 @@ import net.opentsdb.meta.Annotation; import net.opentsdb.meta.TSMeta; import net.opentsdb.meta.UIDMeta; +import net.opentsdb.query.expression.ExpressionFactory; import net.opentsdb.query.filter.TagVFilter; import net.opentsdb.search.SearchPlugin; import net.opentsdb.search.SearchQuery; @@ -208,6 +209,10 @@ public TSDB(final HBaseClient client, final Config config) { uid_cache_map.put(TAG_VALUE_QUAL.getBytes(CHARSET), tag_values); UniqueId.preloadUidCache(this, uid_cache_map); } + + // load up the functions that require the TSDB object + ExpressionFactory.addTSDBFunctions(this); + LOG.debug(config.dumpConfiguration()); } diff --git a/src/query/expression/DivideSeries.java b/src/query/expression/DivideSeries.java new file mode 100644 index 0000000000..cda975cd3f --- /dev/null +++ b/src/query/expression/DivideSeries.java @@ -0,0 +1,86 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import java.util.List; + +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.TSDB; +import net.opentsdb.core.TSQuery; +import net.opentsdb.query.expression.VariableIterator.SetOperator; + +/** + * Performs a UNION set join on up to 26 metric query results and returns the + * quotient. + */ +public class DivideSeries implements Expression { + /** The TSDB used for UID to name lookups */ + final TSDB tsdb; + + /** + * Default ctor. + * @param tsdb The TSDB used for UID to name lookups + */ + public DivideSeries(final TSDB tsdb) { + this.tsdb = tsdb; + } + + @Override + public DataPoints[] evaluate(final TSQuery data_query, + final List query_results, final List params) { + if (data_query == null) { + throw new IllegalArgumentException("Missing time series query"); + } + if (query_results == null || query_results.isEmpty()) { + return new DataPoints[]{}; + } + + if (query_results.size() < 2 || query_results.size() > 26) { + throw new IllegalArgumentException("Must have 2 to 26 series, got " + + query_results.size() + " instead"); + } + + final StringBuilder buf = new StringBuilder(); + char v = 'a'; + for (int i = 0; i < query_results.size(); i++) { + buf.append(v++); + if (i < query_results.size() - 1) { + buf.append(" / "); + } + } + + final ExpressionIterator expression = new ExpressionIterator("divideSeries", + buf.toString(), SetOperator.UNION, false, false); + v = 'a'; + + for (final DataPoints[] dps : query_results) { + final TimeSyncedIterator it = new TimeSyncedIterator( + Character.toString(v++), null, dps); + expression.addResults(it.getId(), it); + } + expression.compile(); + + final DataPoints[] results = new DataPoints[expression.values().length]; + for (int i = 0; i < expression.values().length; i++) { + results[i] = new EDPtoDPS(tsdb, i, expression); + } + return results; + } + + @Override + public String writeStringField(final List query_params, + final String inner_expression) { + return "divideSeries(" + inner_expression + ")"; + } + +} diff --git a/src/query/expression/ExpressionFactory.java b/src/query/expression/ExpressionFactory.java index 3c61528dea..31b876b525 100644 --- a/src/query/expression/ExpressionFactory.java +++ b/src/query/expression/ExpressionFactory.java @@ -15,6 +15,8 @@ import java.util.HashMap; import java.util.Map; +import net.opentsdb.core.TSDB; + /** * A static class that stores and instantiates a static map of the available * functions. @@ -37,6 +39,15 @@ public final class ExpressionFactory { /** Don't instantiate me! */ private ExpressionFactory() { } + /** + * Adds more functions to the map that depend on an instantiated TSDB object. + * Only call this once please. + * @param tsdb The TSDB object to initialize with + */ + public static void addTSDBFunctions(final TSDB tsdb) { + available_functions.put("divideSeries", new DivideSeries(tsdb)); + } + /** * Add an expression to the map. * WARNING: The map is not thread safe so don't use this to dynamically diff --git a/test/query/expression/TestDivideSeries.java b/test/query/expression/TestDivideSeries.java new file mode 100644 index 0000000000..e880a6c31e --- /dev/null +++ b/test/query/expression/TestDivideSeries.java @@ -0,0 +1,197 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import net.opentsdb.core.DataPoint; +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.SeekableView; +import net.opentsdb.core.SeekableViewsForTest; +import net.opentsdb.core.TSQuery; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +@RunWith(PowerMockRunner.class) +@PowerMockIgnore({"javax.management.*", "javax.xml.*", + "ch.qos.*", "org.slf4j.*", + "com.sum.*", "org.xml.*"}) +@PrepareForTest({ TSQuery.class }) +public class TestDivideSeries extends BaseTimeSyncedIteratorTest { + + private static long START_TIME = 1356998400000L; + private static int INTERVAL = 60000; + private static int NUM_POINTS = 5; + + private TSQuery data_query; + private SeekableView view; + private DataPoints dps; + private DataPoints[] group_bys; + private List query_results; + private List params; + private DivideSeries func; + + @Before + public void beforeLocal() throws Exception { + view = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 1, 1); + data_query = mock(TSQuery.class); + when(data_query.startTime()).thenReturn(START_TIME); + when(data_query.endTime()).thenReturn(START_TIME + (INTERVAL * NUM_POINTS)); + + dps = PowerMockito.mock(DataPoints.class); + when(dps.iterator()).thenReturn(view); + when(dps.metricName()).thenReturn(METRIC_STRING); + when(dps.metricUID()).thenReturn(new byte[] {0,0,1}); + + group_bys = new DataPoints[] { dps }; + + query_results = new ArrayList(1); + query_results.add(group_bys); + + params = new ArrayList(1); + func = new DivideSeries(tsdb); + } + + @Test + public void divideOneSeriesEach() throws Exception { + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricUID()).thenReturn(new byte[] {0,0,2}); + group_bys = new DataPoints[] { dps2 }; + query_results.add(group_bys); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(1, results.length); + assertEquals(METRIC_STRING, results[0].metricName()); + + double[] vals= new double[] { 0.1, 0.181, 0.25, 0.307, 0.357 }; + + long ts = START_TIME; + int i = 0; + for (final DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertEquals(vals[i++], dp.toDouble(), 0.001); + ts += INTERVAL; + } + } + + @Test + public void divideMultipleSeriesEach() throws Exception { + oneExtraSameE(); + queryAB_Dstar(); + + query_results.clear(); + query_results.add(results.get("1").getValue()); + query_results.add(results.get("0").getValue()); + final DataPoints[] results = func.evaluate(data_query, + query_results, params); + + assertEquals(3, results.length); + + final int vals[][] = new int[2][]; + vals[0] = new int[] { 11, 1 }; + vals[1] = new int[] { 14, 4 }; + for (int i = 0; i < results.length; i++) { + long ts = 1431561600000l; + final SeekableView it = results[i].iterator(); + while (it.hasNext()) { + final DataPoint dp = it.next(); + assertEquals(ts, dp.timestamp()); + if (i < 2) { + assertEquals(((double)vals[i][0]++ / (double)vals[i][1]++), + dp.toDouble(), 0.001); + } else { + assertEquals(0, dp.toDouble(), 0.0001); + } + ts += INTERVAL; + } + } + } + + @Test (expected = IllegalArgumentException.class) + public void divideOneResultSet() throws Exception { + func.evaluate(data_query, query_results, params); + } + + @Test (expected = IllegalArgumentException.class) + public void divideTooManyResultSets() throws Exception { + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricUID()).thenReturn(new byte[] {0,0,2}); + group_bys = new DataPoints[] { dps2 }; + query_results.add(group_bys); + // doesn't matter what they are + for (int i = 0; i < 100; i++) { + query_results.add(group_bys); + } + + func.evaluate(data_query, query_results, params); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateNullQuery() throws Exception { + params.add("1"); + func.evaluate(null, query_results, params); + } + + @Test + public void evaluateNullResults() throws Exception { + params.add("1"); + final DataPoints[] results = func.evaluate(data_query, null, params); + assertEquals(0, results.length); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateNullParams() throws Exception { + func.evaluate(data_query, query_results, null); + } + + @Test + public void evaluateEmptyResults() throws Exception { + params.add("1"); + final DataPoints[] results = func.evaluate(data_query, + Collections.emptyList(), params); + assertEquals(0, results.length); + } + + @Test + public void writeStringField() throws Exception { + params.add("1"); + assertEquals("divideSeries(inner_expression)", + func.writeStringField(params, "inner_expression")); + assertEquals("divideSeries(null)", func.writeStringField(params, null)); + assertEquals("divideSeries()", func.writeStringField(params, "")); + assertEquals("divideSeries(inner_expression)", + func.writeStringField(null, "inner_expression")); + } +} From 5a5da04eee2b5477cbc51e487c4edeb4e78fc27c Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Fri, 13 Nov 2015 13:02:17 -0800 Subject: [PATCH 079/233] Add the SumSeries function for the Graphite endpoint. Signed-off-by: Chris Larsen --- Makefile.am | 2 + src/query/expression/ExpressionFactory.java | 1 + src/query/expression/SumSeries.java | 85 +++++++++ test/query/expression/TestSumSeries.java | 195 ++++++++++++++++++++ 4 files changed, 283 insertions(+) create mode 100644 src/query/expression/SumSeries.java create mode 100644 test/query/expression/TestSumSeries.java diff --git a/Makefile.am b/Makefile.am index 58e6c3c405..09db54d3c0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -92,6 +92,7 @@ tsdb_SRC := \ src/query/expression/MovingAverage.java \ src/query/expression/PostAggregatedDataPoints.java \ src/query/expression/Scale.java \ + src/query/expression/SumSeries.java \ src/query/expression/TimeSyncedIterator.java \ src/query/expression/UnionIterator.java \ src/query/expression/VariableIterator.java \ @@ -279,6 +280,7 @@ test_SRC := \ test/query/expression/TestMovingAverage.java \ test/query/expression/TestPostAggregatedDataPoints.java \ test/query/expression/TestScale.java \ + test/query/expression/TestSumSeries.java \ test/query/expression/TestTimeSyncedIterator.java \ test/query/expression/TestUnionIterator.java \ test/query/filter/TestTagVFilter.java \ diff --git a/src/query/expression/ExpressionFactory.java b/src/query/expression/ExpressionFactory.java index 31b876b525..49585bae62 100644 --- a/src/query/expression/ExpressionFactory.java +++ b/src/query/expression/ExpressionFactory.java @@ -46,6 +46,7 @@ private ExpressionFactory() { } */ public static void addTSDBFunctions(final TSDB tsdb) { available_functions.put("divideSeries", new DivideSeries(tsdb)); + available_functions.put("sumSeries", new SumSeries(tsdb)); } /** diff --git a/src/query/expression/SumSeries.java b/src/query/expression/SumSeries.java new file mode 100644 index 0000000000..3241901bcc --- /dev/null +++ b/src/query/expression/SumSeries.java @@ -0,0 +1,85 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import java.util.List; + +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.TSDB; +import net.opentsdb.core.TSQuery; +import net.opentsdb.query.expression.VariableIterator.SetOperator; + +/** + * Performs a UNION set join on x metric query results and returns the results. + */ +public class SumSeries implements Expression { + /** The TSDB used for UID to name lookups */ + final TSDB tsdb; + + /** + * Default ctor. + * @param tsdb The TSDB used for UID to name lookups + */ + public SumSeries(final TSDB tsdb) { + this.tsdb = tsdb; + } + + @Override + public DataPoints[] evaluate(final TSQuery data_query, + final List query_results, final List params) { + if (data_query == null) { + throw new IllegalArgumentException("Missing time series query"); + } + if (query_results == null || query_results.isEmpty()) { + return new DataPoints[]{}; + } + + if (query_results.size() < 2 || query_results.size() > 26) { + throw new IllegalArgumentException("Must have 2 to 26 series, got " + + query_results.size() + " instead"); + } + + final StringBuilder buf = new StringBuilder(); + char v = 'a'; + for (int i = 0; i < query_results.size(); i++) { + buf.append(v++); + if (i < query_results.size() - 1) { + buf.append(" + "); + } + } + System.out.println("Expression: [" + buf.toString() + "]"); + final ExpressionIterator expression = new ExpressionIterator("sumSeries", + buf.toString(), SetOperator.UNION, false, false); + v = 'a'; + + for (final DataPoints[] dps : query_results) { + final TimeSyncedIterator it = new TimeSyncedIterator( + Character.toString(v++), null, dps); + expression.addResults(it.getId(), it); + } + expression.compile(); + + final DataPoints[] results = new DataPoints[expression.values().length]; + for (int i = 0; i < expression.values().length; i++) { + results[i] = new EDPtoDPS(tsdb, i, expression); + } + return results; + } + + @Override + public String writeStringField(final List query_params, + final String inner_expression) { + return "sumSeries(" + inner_expression + ")"; + } + +} diff --git a/test/query/expression/TestSumSeries.java b/test/query/expression/TestSumSeries.java new file mode 100644 index 0000000000..77a22b56a3 --- /dev/null +++ b/test/query/expression/TestSumSeries.java @@ -0,0 +1,195 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import net.opentsdb.core.DataPoint; +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.SeekableView; +import net.opentsdb.core.SeekableViewsForTest; +import net.opentsdb.core.TSQuery; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import com.sun.java_cup.internal.runtime.Scanner; + +@RunWith(PowerMockRunner.class) +@PowerMockIgnore({"javax.management.*", "javax.xml.*", + "ch.qos.*", "org.slf4j.*", + "com.sum.*", "org.xml.*"}) +@PrepareForTest({ TSQuery.class, Scanner.class }) +public class TestSumSeries extends BaseTimeSyncedIteratorTest { + + private static long START_TIME = 1356998400000L; + private static int INTERVAL = 60000; + private static int NUM_POINTS = 5; + + private TSQuery data_query; + private SeekableView view; + private DataPoints dps; + private DataPoints[] group_bys; + private List query_results; + private List params; + private SumSeries func; + + @Before + public void beforeLocal() throws Exception { + view = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 1, 1); + data_query = mock(TSQuery.class); + when(data_query.startTime()).thenReturn(START_TIME); + when(data_query.endTime()).thenReturn(START_TIME + (INTERVAL * NUM_POINTS)); + + dps = PowerMockito.mock(DataPoints.class); + when(dps.iterator()).thenReturn(view); + when(dps.metricName()).thenReturn(METRIC_STRING); + when(dps.metricUID()).thenReturn(new byte[] {0,0,1}); + + group_bys = new DataPoints[] { dps }; + + query_results = new ArrayList(1); + query_results.add(group_bys); + + params = new ArrayList(1); + func = new SumSeries(tsdb); + } + + @Test + public void sumOneSeriesEach() throws Exception { + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricUID()).thenReturn(new byte[] {0,0,2}); + group_bys = new DataPoints[] { dps2 }; + query_results.add(group_bys); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(1, results.length); + assertEquals(METRIC_STRING, results[0].metricName()); + + long ts = START_TIME; + double v = 11; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertEquals(v, dp.toDouble(), 0.001); + v += 2; + ts += INTERVAL; + } + } + + @Test + public void sumMultipleSeriesEach() throws Exception { + oneExtraSameE(); + queryAB_Dstar(); + + query_results.clear(); + query_results.add(results.get("1").getValue()); + query_results.add(results.get("0").getValue()); + final DataPoints[] results = func.evaluate(data_query, + query_results, params); + + assertEquals(3, results.length); + + final int vals[] = new int[] { 12, 18, 17 }; + for (int i = 0; i < results.length; i++) { + long ts = 1431561600000l; + final SeekableView it = results[i].iterator(); + while (it.hasNext()) { + final DataPoint dp = it.next(); + assertEquals(ts, dp.timestamp()); + assertEquals(vals[i], dp.toDouble(), 0.0001); + if (i < 2) { + vals[i] += 2; + } else { + vals[i]++; + } + ts += INTERVAL; + } + } + } + + @Test (expected = IllegalArgumentException.class) + public void sumOneResultSet() throws Exception { + func.evaluate(data_query, query_results, params); + } + + @Test (expected = IllegalArgumentException.class) + public void sumTooManyResultSets() throws Exception { + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricUID()).thenReturn(new byte[] {0,0,2}); + group_bys = new DataPoints[] { dps2 }; + // doesn't matter what they are + for (int i = 0; i < 100; i++) { + query_results.add(group_bys); + } + + func.evaluate(data_query, query_results, params); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateNullQuery() throws Exception { + params.add("1"); + func.evaluate(null, query_results, params); + } + + @Test + public void evaluateNullResults() throws Exception { + params.add("1"); + final DataPoints[] results = func.evaluate(data_query, null, params); + assertEquals(0, results.length); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateNullParams() throws Exception { + func.evaluate(data_query, query_results, null); + } + + @Test + public void evaluateEmptyResults() throws Exception { + params.add("1"); + final DataPoints[] results = func.evaluate(data_query, + Collections.emptyList(), params); + assertEquals(0, results.length); + } + + @Test + public void writeStringField() throws Exception { + params.add("1"); + assertEquals("sumSeries(inner_expression)", + func.writeStringField(params, "inner_expression")); + assertEquals("sumSeries(null)", func.writeStringField(params, null)); + assertEquals("sumSeries()", func.writeStringField(params, "")); + assertEquals("sumSeries(inner_expression)", + func.writeStringField(null, "inner_expression")); + } +} From d045bcf429de50d2a06a26b15c3f68f78aca85c9 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Fri, 13 Nov 2015 13:15:25 -0800 Subject: [PATCH 080/233] Add the DiffSeries graphte function Signed-off-by: Chris Larsen --- Makefile.am | 2 + src/query/expression/DiffSeries.java | 85 +++++++++ src/query/expression/ExpressionFactory.java | 1 + test/query/expression/TestDiffSeries.java | 192 ++++++++++++++++++++ 4 files changed, 280 insertions(+) create mode 100644 src/query/expression/DiffSeries.java create mode 100644 test/query/expression/TestDiffSeries.java diff --git a/Makefile.am b/Makefile.am index 09db54d3c0..dd7a2925b8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -76,6 +76,7 @@ tsdb_SRC := \ src/query/QueryUtil.java \ src/query/expression/Absolute.java \ src/query/expression/Expression.java \ + src/query/expression/DiffSeries.java \ src/query/expression/DivideSeries.java \ src/query/expression/Expression.java \ src/query/expression/ExpressionDataPoint.java \ @@ -267,6 +268,7 @@ test_SRC := \ test/meta/TestUIDMeta.java \ test/query/expression/BaseTimeSyncedIteratorTest.java \ test/query/expression/TestAbsolute.java \ + test/query/expression/TestDiffSeries.java \ test/query/expression/TestDivideSeries.java \ test/query/expression/TestExpressionFactory.java \ test/query/expression/TestExpressionIterator.java \ diff --git a/src/query/expression/DiffSeries.java b/src/query/expression/DiffSeries.java new file mode 100644 index 0000000000..be8634b628 --- /dev/null +++ b/src/query/expression/DiffSeries.java @@ -0,0 +1,85 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import java.util.List; + +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.TSDB; +import net.opentsdb.core.TSQuery; +import net.opentsdb.query.expression.VariableIterator.SetOperator; + +/** + * Performs a UNION set join on up to 26 metric query results and returns the + * difference. + */ +public class DiffSeries implements Expression { + /** The TSDB used for UID to name lookups */ + final TSDB tsdb; + + /** + * Default ctor. + * @param tsdb The TSDB used for UID to name lookups + */ + public DiffSeries(final TSDB tsdb) { + this.tsdb = tsdb; + } + + @Override + public DataPoints[] evaluate(final TSQuery data_query, + final List query_results, final List params) { + if (data_query == null) { + throw new IllegalArgumentException("Missing time series query"); + } + if (query_results == null || query_results.isEmpty()) { + return new DataPoints[]{}; + } + + if (query_results.size() < 2 || query_results.size() > 26) { + throw new IllegalArgumentException("Must have 2 to 26 series, got " + + query_results.size() + " instead"); + } + + final StringBuilder buf = new StringBuilder(); + char v = 'a'; + for (int i = 0; i < query_results.size(); i++) { + buf.append(v++); + if (i < query_results.size() - 1) { + buf.append(" - "); + } + } + + final ExpressionIterator expression = new ExpressionIterator("diffSeries", + buf.toString(), SetOperator.UNION, false, false); + v = 'a'; + for (final DataPoints[] dps : query_results) { + final TimeSyncedIterator it = new TimeSyncedIterator( + Character.toString(v++), null, dps); + expression.addResults(it.getId(), it); + } + expression.compile(); + + final DataPoints[] results = new DataPoints[expression.values().length]; + for (int i = 0; i < expression.values().length; i++) { + results[i] = new EDPtoDPS(tsdb, i, expression); + } + return results; + } + + @Override + public String writeStringField(final List query_params, + final String inner_expression) { + return "diffSeries(" + inner_expression + ")"; + } + +} diff --git a/src/query/expression/ExpressionFactory.java b/src/query/expression/ExpressionFactory.java index 49585bae62..432f1f72db 100644 --- a/src/query/expression/ExpressionFactory.java +++ b/src/query/expression/ExpressionFactory.java @@ -47,6 +47,7 @@ private ExpressionFactory() { } public static void addTSDBFunctions(final TSDB tsdb) { available_functions.put("divideSeries", new DivideSeries(tsdb)); available_functions.put("sumSeries", new SumSeries(tsdb)); + available_functions.put("diffSeries", new DiffSeries(tsdb)); } /** diff --git a/test/query/expression/TestDiffSeries.java b/test/query/expression/TestDiffSeries.java new file mode 100644 index 0000000000..3c36e234c1 --- /dev/null +++ b/test/query/expression/TestDiffSeries.java @@ -0,0 +1,192 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import net.opentsdb.core.DataPoint; +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.SeekableView; +import net.opentsdb.core.SeekableViewsForTest; +import net.opentsdb.core.TSQuery; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import com.sun.java_cup.internal.runtime.Scanner; + +@RunWith(PowerMockRunner.class) +@PowerMockIgnore({"javax.management.*", "javax.xml.*", + "ch.qos.*", "org.slf4j.*", + "com.sum.*", "org.xml.*"}) +@PrepareForTest({ TSQuery.class, Scanner.class }) +public class TestDiffSeries extends BaseTimeSyncedIteratorTest { + + private static long START_TIME = 1356998400000L; + private static int INTERVAL = 60000; + private static int NUM_POINTS = 5; + + private TSQuery data_query; + private SeekableView view; + private DataPoints dps; + private DataPoints[] group_bys; + private List query_results; + private List params; + private DiffSeries func; + + @Before + public void beforeLocal() throws Exception { + view = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 1, 1); + data_query = mock(TSQuery.class); + when(data_query.startTime()).thenReturn(START_TIME); + when(data_query.endTime()).thenReturn(START_TIME + (INTERVAL * NUM_POINTS)); + + dps = PowerMockito.mock(DataPoints.class); + when(dps.iterator()).thenReturn(view); + when(dps.metricName()).thenReturn(METRIC_STRING); + when(dps.metricUID()).thenReturn(new byte[] {0,0,1}); + + group_bys = new DataPoints[] { dps }; + + query_results = new ArrayList(1); + query_results.add(group_bys); + + params = new ArrayList(1); + func = new DiffSeries(tsdb); + } + + @Test + public void diffOneSeriesEach() throws Exception { + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricUID()).thenReturn(new byte[] {0,0,2}); + group_bys = new DataPoints[] { dps2 }; + query_results.add(group_bys); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(1, results.length); + assertEquals(METRIC_STRING, results[0].metricName()); + + long ts = START_TIME; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertEquals(-9, dp.toDouble(), 0.001); + ts += INTERVAL; + } + } + + @Test + public void diffMultipleSeriesEach() throws Exception { + oneExtraSameE(); + queryAB_Dstar(); + + query_results.clear(); + query_results.add(results.get("1").getValue()); + query_results.add(results.get("0").getValue()); + final DataPoints[] results = func.evaluate(data_query, + query_results, params); + + assertEquals(3, results.length); + + double val = 17; + for (int i = 0; i < results.length; i++) { + long ts = 1431561600000l; + final SeekableView it = results[i].iterator(); + while (it.hasNext()) { + final DataPoint dp = it.next(); + assertEquals(ts, dp.timestamp()); + if (i < 2) { + assertEquals(10, dp.toDouble(), 0.0001); + } else { + assertEquals(val++, dp.toDouble(), 0.0001); + } + ts += INTERVAL; + } + } + } + + @Test (expected = IllegalArgumentException.class) + public void diffOneResultSet() throws Exception { + func.evaluate(data_query, query_results, params); + } + + @Test (expected = IllegalArgumentException.class) + public void diffTooManyResultSets() throws Exception { + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricUID()).thenReturn(new byte[] {0,0,2}); + group_bys = new DataPoints[] { dps2 }; + // doesn't matter what they are + for (int i = 0; i < 100; i++) { + query_results.add(group_bys); + } + + func.evaluate(data_query, query_results, params); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateNullQuery() throws Exception { + params.add("1"); + func.evaluate(null, query_results, params); + } + + @Test + public void evaluateNullResults() throws Exception { + params.add("1"); + final DataPoints[] results = func.evaluate(data_query, null, params); + assertEquals(0, results.length); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateNullParams() throws Exception { + func.evaluate(data_query, query_results, null); + } + + @Test + public void evaluateEmptyResults() throws Exception { + params.add("1"); + final DataPoints[] results = func.evaluate(data_query, + Collections.emptyList(), params); + assertEquals(0, results.length); + } + + @Test + public void writeStringField() throws Exception { + params.add("1"); + assertEquals("diffSeries(inner_expression)", + func.writeStringField(params, "inner_expression")); + assertEquals("diffSeries(null)", func.writeStringField(params, null)); + assertEquals("diffSeries()", func.writeStringField(params, "")); + assertEquals("diffSeries(inner_expression)", + func.writeStringField(null, "inner_expression")); + } +} From 74931051380142a8b272a4977d327be706713d2b Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Fri, 13 Nov 2015 15:25:19 -0800 Subject: [PATCH 081/233] Add the MultiplySeries function for Graphite. Signed-off-by: Chris Larsen --- Makefile.am | 2 + src/query/expression/ExpressionFactory.java | 1 + src/query/expression/MultiplySeries.java | 86 ++++++++ test/query/expression/TestMultiplySeries.java | 196 ++++++++++++++++++ 4 files changed, 285 insertions(+) create mode 100644 src/query/expression/MultiplySeries.java create mode 100644 test/query/expression/TestMultiplySeries.java diff --git a/Makefile.am b/Makefile.am index dd7a2925b8..20b57ea29c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -91,6 +91,7 @@ tsdb_SRC := \ src/query/expression/ITimeSyncedIterator.java \ src/query/expression/NumericFillPolicy.java \ src/query/expression/MovingAverage.java \ + src/query/expression/MultiplySeries.java \ src/query/expression/PostAggregatedDataPoints.java \ src/query/expression/Scale.java \ src/query/expression/SumSeries.java \ @@ -280,6 +281,7 @@ test_SRC := \ test/query/expression/TestIntersectionIterator.java \ test/query/expression/TestNumericFillPolicy.java \ test/query/expression/TestMovingAverage.java \ + test/query/expression/TestMultiplySeries.java \ test/query/expression/TestPostAggregatedDataPoints.java \ test/query/expression/TestScale.java \ test/query/expression/TestSumSeries.java \ diff --git a/src/query/expression/ExpressionFactory.java b/src/query/expression/ExpressionFactory.java index 432f1f72db..c2d9ecd246 100644 --- a/src/query/expression/ExpressionFactory.java +++ b/src/query/expression/ExpressionFactory.java @@ -48,6 +48,7 @@ public static void addTSDBFunctions(final TSDB tsdb) { available_functions.put("divideSeries", new DivideSeries(tsdb)); available_functions.put("sumSeries", new SumSeries(tsdb)); available_functions.put("diffSeries", new DiffSeries(tsdb)); + available_functions.put("multiplySeries", new MultiplySeries(tsdb)); } /** diff --git a/src/query/expression/MultiplySeries.java b/src/query/expression/MultiplySeries.java new file mode 100644 index 0000000000..34cbb251d8 --- /dev/null +++ b/src/query/expression/MultiplySeries.java @@ -0,0 +1,86 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import java.util.List; + +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.TSDB; +import net.opentsdb.core.TSQuery; +import net.opentsdb.query.expression.VariableIterator.SetOperator; + +/** + * Performs a UNION set join on up to 26 metric query results and returns the + * product. + */ +public class MultiplySeries implements Expression { + /** The TSDB used for UID to name lookups */ + final TSDB tsdb; + + /** + * Default ctor. + * @param tsdb The TSDB used for UID to name lookups + */ + public MultiplySeries(final TSDB tsdb) { + this.tsdb = tsdb; + } + + @Override + public DataPoints[] evaluate(final TSQuery data_query, + final List query_results, final List params) { + if (data_query == null) { + throw new IllegalArgumentException("Missing time series query"); + } + if (query_results == null || query_results.isEmpty()) { + return new DataPoints[]{}; + } + + if (query_results.size() < 2 || query_results.size() > 26) { + throw new IllegalArgumentException("Must have 2 to 26 series, got " + + query_results.size() + " instead"); + } + + final StringBuilder buf = new StringBuilder(); + char v = 'a'; + for (int i = 0; i < query_results.size(); i++) { + buf.append(v++); + if (i < query_results.size() - 1) { + buf.append(" * "); + } + } + + final ExpressionIterator expression = new ExpressionIterator("multiplySeries", + buf.toString(), SetOperator.UNION, false, false); + v = 'a'; + + for (final DataPoints[] dps : query_results) { + final TimeSyncedIterator it = new TimeSyncedIterator( + Character.toString(v++), null, dps); + expression.addResults(it.getId(), it); + } + expression.compile(); + + final DataPoints[] results = new DataPoints[expression.values().length]; + for (int i = 0; i < expression.values().length; i++) { + results[i] = new EDPtoDPS(tsdb, i, expression); + } + return results; + } + + @Override + public String writeStringField(final List query_params, + final String inner_expression) { + return "multiplySeries(" + inner_expression + ")"; + } + +} diff --git a/test/query/expression/TestMultiplySeries.java b/test/query/expression/TestMultiplySeries.java new file mode 100644 index 0000000000..292f975f47 --- /dev/null +++ b/test/query/expression/TestMultiplySeries.java @@ -0,0 +1,196 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import net.opentsdb.core.DataPoint; +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.SeekableView; +import net.opentsdb.core.SeekableViewsForTest; +import net.opentsdb.core.TSQuery; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import com.sun.java_cup.internal.runtime.Scanner; + +@RunWith(PowerMockRunner.class) +@PowerMockIgnore({"javax.management.*", "javax.xml.*", + "ch.qos.*", "org.slf4j.*", + "com.sum.*", "org.xml.*"}) +@PrepareForTest({ TSQuery.class, Scanner.class }) +public class TestMultiplySeries extends BaseTimeSyncedIteratorTest { + + private static long START_TIME = 1356998400000L; + private static int INTERVAL = 60000; + private static int NUM_POINTS = 5; + + private TSQuery data_query; + private SeekableView view; + private DataPoints dps; + private DataPoints[] group_bys; + private List query_results; + private List params; + private MultiplySeries func; + + @Before + public void beforeLocal() throws Exception { + view = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 1, 1); + data_query = mock(TSQuery.class); + when(data_query.startTime()).thenReturn(START_TIME); + when(data_query.endTime()).thenReturn(START_TIME + (INTERVAL * NUM_POINTS)); + + dps = PowerMockito.mock(DataPoints.class); + when(dps.iterator()).thenReturn(view); + when(dps.metricName()).thenReturn(METRIC_STRING); + when(dps.metricUID()).thenReturn(new byte[] {0,0,1}); + + group_bys = new DataPoints[] { dps }; + + query_results = new ArrayList(1); + query_results.add(group_bys); + + params = new ArrayList(1); + func = new MultiplySeries(tsdb); + } + + @Test + public void multiplyOneSeriesEach() throws Exception { + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricUID()).thenReturn(new byte[] {0,0,2}); + group_bys = new DataPoints[] { dps2 }; + query_results.add(group_bys); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(1, results.length); + assertEquals(METRIC_STRING, results[0].metricName()); + final int[] vals = new int[] { 10, 22, 36, 52, 70 }; + int i = 0; + long ts = START_TIME; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertEquals(vals[i++], dp.toDouble(), 0.001); + ts += INTERVAL; + } + } + + @Test + public void multiplyMultipleSeriesEach() throws Exception { + oneExtraSameE(); + queryAB_Dstar(); + + query_results.clear(); + query_results.add(results.get("1").getValue()); + query_results.add(results.get("0").getValue()); + final DataPoints[] results = func.evaluate(data_query, + query_results, params); + + assertEquals(3, results.length); + + double[][] vals = new double[2][]; + vals[0] = new double[] { 11, 24, 39 }; + vals[1] = new double[] { 56, 75, 96 }; + for (int i = 0; i < results.length; i++) { + long ts = 1431561600000l; + final SeekableView it = results[i].iterator(); + int x = 0; + while (it.hasNext()) { + final DataPoint dp = it.next(); + assertEquals(ts, dp.timestamp()); + if (i < 2) { + assertEquals(vals[i][x++], dp.toDouble(), 0.0001); + } else { + assertEquals(0, dp.toDouble(), 0.0001); + } + ts += INTERVAL; + } + } + } + + @Test (expected = IllegalArgumentException.class) + public void multiplyOneResultSet() throws Exception { + func.evaluate(data_query, query_results, params); + } + + @Test (expected = IllegalArgumentException.class) + public void multiplyTooManyResultSets() throws Exception { + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricUID()).thenReturn(new byte[] {0,0,2}); + group_bys = new DataPoints[] { dps2 }; + // doesn't matter what they are + for (int i = 0; i < 100; i++) { + query_results.add(group_bys); + } + + func.evaluate(data_query, query_results, params); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateNullQuery() throws Exception { + params.add("1"); + func.evaluate(null, query_results, params); + } + + @Test + public void evaluateNullResults() throws Exception { + params.add("1"); + final DataPoints[] results = func.evaluate(data_query, null, params); + assertEquals(0, results.length); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateNullParams() throws Exception { + func.evaluate(data_query, query_results, null); + } + + @Test + public void evaluateEmptyResults() throws Exception { + params.add("1"); + final DataPoints[] results = func.evaluate(data_query, + Collections.emptyList(), params); + assertEquals(0, results.length); + } + + @Test + public void writeStringField() throws Exception { + params.add("1"); + assertEquals("multiplySeries(inner_expression)", + func.writeStringField(params, "inner_expression")); + assertEquals("multiplySeries(null)", func.writeStringField(params, null)); + assertEquals("multiplySeries()", func.writeStringField(params, "")); + assertEquals("multiplySeries(inner_expression)", + func.writeStringField(null, "inner_expression")); + } +} From 3a268f7cb6e7f5a5c91be00a9b76e85b58df8631 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Thu, 12 Nov 2015 13:38:55 -0800 Subject: [PATCH 082/233] Add single series iteration interfaces so we can use expressions but still output in the TSD 2.0 format where we display one series at a time instead of iterating over them all at once in lock-step. Signed-off-by: Chris Larsen --- src/query/expression/ExpressionDataPoint.java | 12 ++++ src/query/expression/ExpressionIterator.java | 34 +++++++++++ src/query/expression/ITimeSyncedIterator.java | 16 ++++- .../expression/IntersectionIterator.java | 21 +++++++ src/query/expression/TimeSyncedIterator.java | 34 +++++++---- src/query/expression/UnionIterator.java | 43 ++++++++++++- src/query/expression/VariableIterator.java | 14 +++++ .../expression/TestExpressionIterator.java | 61 +++++++++++++------ 8 files changed, 202 insertions(+), 33 deletions(-) diff --git a/src/query/expression/ExpressionDataPoint.java b/src/query/expression/ExpressionDataPoint.java index 216546869f..ea85cc3652 100644 --- a/src/query/expression/ExpressionDataPoint.java +++ b/src/query/expression/ExpressionDataPoint.java @@ -56,6 +56,9 @@ public class ExpressionDataPoint implements DataPoint { /** The data point overwritten each time through the iterator */ private final MutableDataPoint dp; + /** An index in the original {@link TimeSyncedIterator} iterator array */ + private int index; + /** * Default ctor that simply sets up new objects for all internal fields. * TODO - lazily initialize the field to avoid unused objects @@ -224,4 +227,13 @@ public double toDouble() { return dp.toDouble(); } + /** @param index The index in the {@link TimeSyncedIterator} array */ + public void setIndex(final int index) { + this.index = index; + } + + /** @return the index in the {@link TimeSyncedIterator} array */ + public int getIndex() { + return index; + } } diff --git a/src/query/expression/ExpressionIterator.java b/src/query/expression/ExpressionIterator.java index c7d12c78ba..6684c60088 100644 --- a/src/query/expression/ExpressionIterator.java +++ b/src/query/expression/ExpressionIterator.java @@ -414,4 +414,38 @@ public ITimeSyncedIterator getCopy() { final ExpressionIterator ei = new ExpressionIterator(this); return ei; } + + @Override + public boolean hasNext(final int i) { + return iterator.hasNext(i); + } + + @Override + public void next(final int i) { + iterator.next(i); + + // set aside a couple of addresses for the variables + double val; + double result; + // this here is why life sucks. there MUST be a better way to bind variables + long ts = Long.MAX_VALUE; + for (final String variable : names) { + if (iteration_results.get(variable)[i] == null) { + context.set(variable, results.get(variable).getFillPolicy().getValue()); + } else { + if (iteration_results.get(variable)[i].timestamp() < ts) { + ts = iteration_results.get(variable)[i].timestamp(); + } + val = iteration_results.get(variable)[i].toDouble(); + if (Double.isNaN(val)) { + context.set(variable, results.get(variable).getFillPolicy().getValue()); + } else { + context.set(variable, val); + } + } + } + result = (Double)expression.execute(context); + dps[i].reset(ts, result); + } + } diff --git a/src/query/expression/ITimeSyncedIterator.java b/src/query/expression/ITimeSyncedIterator.java index 22efbc55ae..b72a0df401 100644 --- a/src/query/expression/ITimeSyncedIterator.java +++ b/src/query/expression/ITimeSyncedIterator.java @@ -32,11 +32,25 @@ public interface ITimeSyncedIterator { */ public ExpressionDataPoint[] next(final long timestamp); + /** + * Determines whether the individual series in the {@link values} array has + * another value. This may be used for non-synchronous iteration. + * @param index The index of the series in the values array to check for + * @return True if the series has another value, false if not + */ + public boolean hasNext(final int index); + + /** + * Fetches the next value for an individual series in the {@link values} array. + * @param index The index of the series in the values array to advance + */ + public void next(final int index); + /** * @return the next timestamp available in this set. */ public long nextTimestamp(); - + /** @return the number of series in this set */ public int size(); diff --git a/src/query/expression/IntersectionIterator.java b/src/query/expression/IntersectionIterator.java index 99e1e3ae7c..40d01068a0 100644 --- a/src/query/expression/IntersectionIterator.java +++ b/src/query/expression/IntersectionIterator.java @@ -497,4 +497,25 @@ public NumericFillPolicy getFillPolicy() { public ITimeSyncedIterator getCopy() { return new IntersectionIterator(this); } + + @Override + public boolean hasNext(int index) { + for (final ITimeSyncedIterator sub : queries.values()) { + if (sub.hasNext(index)) { + return true; + } + } + return false; + } + + @Override + public void next(int index) { + if (!hasNext()) { + throw new IllegalDataException("No more data"); + } + for (final ITimeSyncedIterator sub : queries.values()) { + sub.next(index); + } + } + } diff --git a/src/query/expression/TimeSyncedIterator.java b/src/query/expression/TimeSyncedIterator.java index 58d562b0cd..451e7b96c2 100644 --- a/src/query/expression/TimeSyncedIterator.java +++ b/src/query/expression/TimeSyncedIterator.java @@ -133,7 +133,11 @@ public ExpressionDataPoint[] next(final long timestamp) { emitter_values[i].reset(timestamp, fill_policy.getValue()); } else { emitter_values[i].reset(current_values[i]); - next(i); // move to the next value for this guy + if (!iterators[i].hasNext()) { + current_values[i] = null; + } else { + current_values[i] = iterators[i].next(); + } } } return emitter_values; @@ -152,20 +156,25 @@ public long nextTimestamp() { } return ts; } - - /** - * Moves the selected series to the next value. If the iterator has been - * nulled out (no more data) then this is a no-op. - * @param i The iterator index to advance - */ - private void next(final int i) { - if (!iterators[i].hasNext()) { + + @Override + public void next(final int i) { + if (current_values[i] == null) { + throw new RuntimeException("No more elements"); + } + emitter_values[i].reset(current_values[i]); + if (iterators[i].hasNext()) { + current_values[i] = iterators[i].next(); + } else { current_values[i] = null; - return; } - current_values[i] = iterators[i].next(); } - + + @Override + public boolean hasNext(final int i) { + return current_values[i] != null; + } + @Override public int getIndex() { return index; @@ -232,6 +241,7 @@ private void setupEmitters() { } else { current_values[i] = iterators[i].next(); emitter_values[i] = new ExpressionDataPoint(dps[i]); + emitter_values[i].setIndex(i); } } } diff --git a/src/query/expression/UnionIterator.java b/src/query/expression/UnionIterator.java index 0ed4514aa1..6453791c06 100644 --- a/src/query/expression/UnionIterator.java +++ b/src/query/expression/UnionIterator.java @@ -46,6 +46,9 @@ public class UnionIterator implements ITimeSyncedIterator, VariableIterator { /** A list of the current values for each series post intersection */ private final Map current_values; + /** A map used for single series iteration where the array is the index */ + private final Map single_series_matrix; + /** A map of the sub query index to their names for intersection computation */ private final String[] index_to_names; @@ -92,6 +95,7 @@ public UnionIterator(final String id, final Map res timestamp = Long.MAX_VALUE; queries = new HashMap(results.size()); current_values = new HashMap(results.size()); + single_series_matrix = new HashMap(results.size()); index_to_names = new String[results.size()]; fill_policy = new NumericFillPolicy(FillPolicy.ZERO); fill_dp = new ExpressionDataPoint(); @@ -134,6 +138,7 @@ private UnionIterator(final UnionIterator iterator) { timestamp = Long.MAX_VALUE; queries = new HashMap(iterator.queries.size()); current_values = new HashMap(queries.size()); + single_series_matrix = new HashMap(queries.size()); index_to_names = new String[queries.size()]; fill_policy = iterator.fill_policy; @@ -204,14 +209,25 @@ private void setCurrentAndMeta(final ByteMap ordered_union) { for (final String id : queries.keySet()) { current_values.put(id, new ExpressionDataPoint[ordered_union.size()]); + // TODO - blech. Fill with a sentinel value to reflect "no data here!" + final int[] m = new int[ordered_union.size()]; + for (int i = 0; i < m.length; i++) { + m[i] = -1; + } + single_series_matrix.put(id, m); } int i = 0; - for (final ExpressionDataPoint[] idps : ordered_union.values()) { + for (final Entry entry : ordered_union.entrySet()) { + final ExpressionDataPoint[] idps = entry.getValue(); for (int x = 0; x < idps.length; x++) { final ExpressionDataPoint[] current_dps = current_values.get(index_to_names[x]); current_dps[i] = idps[x]; + final int[] m = single_series_matrix.get(index_to_names[x]); + if (idps[x] != null) { + m[i] = idps[x].getIndex(); + } } ++i; } @@ -411,4 +427,29 @@ public Map getResults() { public int getSeriesSize() { return series_size; } + + @Override + public boolean hasNext(int index) { + for (final Entry entry : single_series_matrix.entrySet()) { + final int idx = entry.getValue()[index]; + if (idx >= 0 && queries.get(entry.getKey()).hasNext(idx)) { + return true; + } + } + return false; + } + + @Override + public void next(int index) { + if (!hasNext()) { + throw new IllegalDataException("No more data"); + } + for (final Entry entry : single_series_matrix.entrySet()) { + final int idx = entry.getValue()[index]; + if (idx >= 0) { + queries.get(entry.getKey()).next(idx); + } + } + } + } diff --git a/src/query/expression/VariableIterator.java b/src/query/expression/VariableIterator.java index 0ed7c6dab8..9bfba0b889 100644 --- a/src/query/expression/VariableIterator.java +++ b/src/query/expression/VariableIterator.java @@ -80,6 +80,20 @@ public static SetOperator fromString(final String name) { * call {@link hasNext()} first. */ public void next(); + + /** + * Determines whether the individual series in the {@link values} array has + * another value. This may be used for non-synchronous iteration. + * @param index The index of the series in the values array to check for + * @return True if the series has another value, false if not + */ + public boolean hasNext(final int index); + + /** + * Fetches the next value for an individual series in the {@link values} array. + * @param index The index of the series in the values array to advance + */ + public void next(final int index); /** * Returns a map of variable names to result series. You can maintain the diff --git a/test/query/expression/TestExpressionIterator.java b/test/query/expression/TestExpressionIterator.java index 9c98a5071c..cd90c88d31 100644 --- a/test/query/expression/TestExpressionIterator.java +++ b/test/query/expression/TestExpressionIterator.java @@ -1019,8 +1019,8 @@ public void unionNoIntersection() throws Exception { } @Test - public void scratch() throws Exception { - threeDifE(); + public void unionSingleSeriesIteration() throws Exception { + oneExtraSameE(); queryAB_Dstar(); remapResults(); @@ -1031,28 +1031,51 @@ public void scratch() throws Exception { exp.compile(); final ExpressionDataPoint[] dps = exp.values(); - assertEquals(6, dps.length); - //validateMeta(dps, true); + double[] values = new double[] { 12, 18, 17 }; - long ts = 1431561600000L; - double[] values = new double[] { 1, 11, 4, 14, 7, 17 }; - long its = exp.nextTimestamp(); - while (exp.hasNext()) { - exp.next(its); - for (int i = 0; i < dps.length; i++) { - System.out.println(dps[i].timestamp() + " " + dps[i].toDouble()); + for (int i = 0; i < dps.length; i++) { + long ts = 1431561600000L; + while (exp.hasNext(i)) { + exp.next(i); + assertEquals(ts, dps[i].timestamp()); + assertEquals(values[i], dps[i].toDouble(), 0.001); + + ts += 60000; + if (i < dps.length - 1) { + values[i] += 2; + } else { + values[i]++; + } } - - for (int i = 0; i < values.length; i++) { + } + } + + @Test + public void intersectionSingleSeriesIteration() throws Exception { + oneExtraSameE(); + queryAB_Dstar(); + remapResults(); + + ExpressionIterator exp = new ExpressionIterator("ei", "a + b", + SetOperator.INTERSECTION, false, false); + exp.addResults("a", iterators.get("a")); + exp.addResults("b", iterators.get("b")); + + exp.compile(); + final ExpressionDataPoint[] dps = exp.values(); + double[] values = new double[] { 12, 18 }; + + for (int i = 0; i < dps.length; i++) { + long ts = 1431561600000L; + while (exp.hasNext(i)) { + exp.next(i); assertEquals(ts, dps[i].timestamp()); - assertEquals(values[i], dps[i].toDouble(), 0.0001); - ++values[i]; + assertEquals(values[i], dps[i].toDouble(), 0.001); + ts += 60000; + values[i] += 2; } - - ts += 60000; - its = exp.nextTimestamp(); } - validateMeta(dps, false); + } /** From 956d63b23bec6d737f842058b7f47c22b2108e8c Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Thu, 12 Nov 2015 15:03:43 -0800 Subject: [PATCH 083/233] Null checks in the ExpressionDataPoint ctor for unit testing. Signed-off-by: Chris Larsen --- src/query/expression/ExpressionDataPoint.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/query/expression/ExpressionDataPoint.java b/src/query/expression/ExpressionDataPoint.java index ea85cc3652..35c7807af1 100644 --- a/src/query/expression/ExpressionDataPoint.java +++ b/src/query/expression/ExpressionDataPoint.java @@ -79,10 +79,13 @@ public ExpressionDataPoint() { public ExpressionDataPoint(final DataPoints dps) { metric_uids = new ByteSet(); metric_uids.add(dps.metricUID()); - tags = (ByteMap) dps.getTagUids().clone(); + tags = dps.getTagUids() != null ? + (ByteMap) dps.getTagUids().clone() : new ByteMap(); aggregated_tags = new ByteSet(); - for (final byte[] tagk : dps.getAggregatedTagUids()) { - aggregated_tags.add(tagk); + if (dps.getAggregatedTagUids() != null) { + for (final byte[] tagk : dps.getAggregatedTagUids()) { + aggregated_tags.add(tagk); + } } tsuids = new HashSet(dps.getTSUIDs()); // TODO - restore when these are faster From bfa40b1bd8d5a02c9c6e57541aeb797d33742b1a Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sun, 15 Nov 2015 12:43:13 -0800 Subject: [PATCH 084/233] Add the EDPtoDPS.java class to the makefile (whoops!) Signed-off-by: Chris Larsen --- Makefile.am | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile.am b/Makefile.am index 20b57ea29c..bcb4733c35 100644 --- a/Makefile.am +++ b/Makefile.am @@ -78,6 +78,7 @@ tsdb_SRC := \ src/query/expression/Expression.java \ src/query/expression/DiffSeries.java \ src/query/expression/DivideSeries.java \ + src/query/expression/EDPtoDPS.java \ src/query/expression/Expression.java \ src/query/expression/ExpressionDataPoint.java \ src/query/expression/ExpressionFactory.java \ From 682a32a18920857898b44b98e7ec0953ea098651 Mon Sep 17 00:00:00 2001 From: Jonathan Creasy Date: Wed, 18 Nov 2015 15:02:24 -0600 Subject: [PATCH 085/233] Expression.java was included twice in Makefile Signed-off-by: Chris Larsen --- Makefile.am | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index bcb4733c35..a65db199f7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -75,7 +75,6 @@ tsdb_SRC := \ src/meta/UIDMeta.java \ src/query/QueryUtil.java \ src/query/expression/Absolute.java \ - src/query/expression/Expression.java \ src/query/expression/DiffSeries.java \ src/query/expression/DivideSeries.java \ src/query/expression/EDPtoDPS.java \ From 2588419f5724b38be8868e9c5e8d8fe2de9bc6e8 Mon Sep 17 00:00:00 2001 From: Jonathan Creasy Date: Wed, 18 Nov 2015 14:49:05 -0600 Subject: [PATCH 086/233] A few changes to the expression support. * Added convienience aliases for SumSeries, DiffSeries, MultiplySeries, and DivideSeries * Added Alias function Signed-off-by: Chris Larsen --- Makefile.am | 1 + src/query/expression/Alias.java | 89 +++++++++++++++++++++ src/query/expression/ExpressionFactory.java | 5 ++ 3 files changed, 95 insertions(+) create mode 100644 src/query/expression/Alias.java diff --git a/Makefile.am b/Makefile.am index a65db199f7..89a4b656c8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -75,6 +75,7 @@ tsdb_SRC := \ src/meta/UIDMeta.java \ src/query/QueryUtil.java \ src/query/expression/Absolute.java \ + src/query/expression/Alias.java \ src/query/expression/DiffSeries.java \ src/query/expression/DivideSeries.java \ src/query/expression/EDPtoDPS.java \ diff --git a/src/query/expression/Alias.java b/src/query/expression/Alias.java new file mode 100644 index 0000000000..410d409fd2 --- /dev/null +++ b/src/query/expression/Alias.java @@ -0,0 +1,89 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import java.util.List; +import java.util.Map; +import com.google.common.base.Joiner; + +import net.opentsdb.core.DataPoint; +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.MutableDataPoint; +import net.opentsdb.core.TSQuery; + +/** + * Returns an alias if provided or the original metric name if not. + * @since 2.3 + */ +public class Alias implements Expression { + + static Joiner COMMA_JOINER = Joiner.on(',').skipNulls(); + + @Override + public DataPoints[] evaluate(TSQuery data_query, List queryResults, + List queryParams) { + if (queryResults == null || queryResults.size() == 0) { + throw new NullPointerException("No query results"); + } + + String aliasTemplate = "__default"; + + if (queryParams != null && queryParams.size() >= 0) { + aliasTemplate = COMMA_JOINER.join(queryParams); + } + + DataPoints[] inputPoints = queryResults.get(0); + + DataPoint[][] dps = new DataPoint[inputPoints.length][]; + + for (int j = 0; j < dps.length; j++) { + DataPoints base = inputPoints[j]; + dps[j] = new DataPoint[base.size()]; + int i = 0; + + for (DataPoint pt : base) { + if (pt.isInteger()) { + dps[j][i] = MutableDataPoint.ofDoubleValue(pt.timestamp(), pt.longValue()); + } else { + dps[j][i] = MutableDataPoint.ofDoubleValue(pt.timestamp(), pt.doubleValue()); + } + i++; + } + } + + DataPoints[] resultArray = new DataPoints[queryResults.get(0).length]; + for (int i = 0; i < resultArray.length; i++) { + PostAggregatedDataPoints result = new PostAggregatedDataPoints(inputPoints[i], + dps[i]); + + String alias = aliasTemplate; + for (Map.Entry e : inputPoints[i].getTags().entrySet()) { + alias = alias.replace("@" + e.getKey(), e.getValue()); + } + + result.setAlias(alias); + resultArray[i] = result; + } + + return resultArray; + } + + @Override + public String writeStringField(List queryParams, String innerExpression) { + if (queryParams == null || queryParams.size() == 0) { + return "NULL"; + } + + return queryParams.get(0); + } +} \ No newline at end of file diff --git a/src/query/expression/ExpressionFactory.java b/src/query/expression/ExpressionFactory.java index c2d9ecd246..23bd39d1dd 100644 --- a/src/query/expression/ExpressionFactory.java +++ b/src/query/expression/ExpressionFactory.java @@ -29,6 +29,7 @@ public final class ExpressionFactory { new HashMap(); static { + available_functions.put("alias", new Alias()); available_functions.put("scale", new Scale()); available_functions.put("absolute", new Absolute()); available_functions.put("movingAverage", new MovingAverage()); @@ -46,9 +47,13 @@ private ExpressionFactory() { } */ public static void addTSDBFunctions(final TSDB tsdb) { available_functions.put("divideSeries", new DivideSeries(tsdb)); + available_functions.put("divide", new DivideSeries(tsdb)); available_functions.put("sumSeries", new SumSeries(tsdb)); + available_functions.put("sum", new SumSeries(tsdb)); available_functions.put("diffSeries", new DiffSeries(tsdb)); + available_functions.put("difference", new DiffSeries(tsdb)); available_functions.put("multiplySeries", new MultiplySeries(tsdb)); + available_functions.put("multiply", new MultiplySeries(tsdb)); } /** From c434dc1c3dd94b3ed07516b98070b672e8eb32fe Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Fri, 20 Nov 2015 16:40:41 -0800 Subject: [PATCH 087/233] Cleanup Alias and fix it so that it handles tag resolution in the alias template to resolve asynchronously. Also add unit tests. Signed-off-by: Chris Larsen --- Makefile.am | 1 + src/query/expression/Alias.java | 125 +++---- .../expression/PostAggregatedDataPoints.java | 28 +- test/query/expression/TestAlias.java | 321 ++++++++++++++++++ 4 files changed, 414 insertions(+), 61 deletions(-) create mode 100644 test/query/expression/TestAlias.java diff --git a/Makefile.am b/Makefile.am index 89a4b656c8..7ce950db64 100644 --- a/Makefile.am +++ b/Makefile.am @@ -270,6 +270,7 @@ test_SRC := \ test/meta/TestUIDMeta.java \ test/query/expression/BaseTimeSyncedIteratorTest.java \ test/query/expression/TestAbsolute.java \ + test/query/expression/TestAlias.java \ test/query/expression/TestDiffSeries.java \ test/query/expression/TestDivideSeries.java \ test/query/expression/TestExpressionFactory.java \ diff --git a/src/query/expression/Alias.java b/src/query/expression/Alias.java index 410d409fd2..737d1c46ba 100644 --- a/src/query/expression/Alias.java +++ b/src/query/expression/Alias.java @@ -12,78 +12,89 @@ // see . package net.opentsdb.query.expression; +import java.util.ArrayList; import java.util.List; -import java.util.Map; + import com.google.common.base.Joiner; import net.opentsdb.core.DataPoint; import net.opentsdb.core.DataPoints; import net.opentsdb.core.MutableDataPoint; +import net.opentsdb.core.SeekableView; import net.opentsdb.core.TSQuery; /** - * Returns an alias if provided or the original metric name if not. + * Returns an alias if provided or the original metric name if not. The alias + * may optionally contain a template for tag replacement so that tags are + * advanced to the metric name for systems that require it. (e.g. flatten + * a name for Graphite). * @since 2.3 */ public class Alias implements Expression { - static Joiner COMMA_JOINER = Joiner.on(',').skipNulls(); - - @Override - public DataPoints[] evaluate(TSQuery data_query, List queryResults, - List queryParams) { - if (queryResults == null || queryResults.size() == 0) { - throw new NullPointerException("No query results"); - } - - String aliasTemplate = "__default"; - - if (queryParams != null && queryParams.size() >= 0) { - aliasTemplate = COMMA_JOINER.join(queryParams); - } - - DataPoints[] inputPoints = queryResults.get(0); - - DataPoint[][] dps = new DataPoint[inputPoints.length][]; - - for (int j = 0; j < dps.length; j++) { - DataPoints base = inputPoints[j]; - dps[j] = new DataPoint[base.size()]; - int i = 0; - - for (DataPoint pt : base) { - if (pt.isInteger()) { - dps[j][i] = MutableDataPoint.ofDoubleValue(pt.timestamp(), pt.longValue()); - } else { - dps[j][i] = MutableDataPoint.ofDoubleValue(pt.timestamp(), pt.doubleValue()); - } - i++; - } - } - - DataPoints[] resultArray = new DataPoints[queryResults.get(0).length]; - for (int i = 0; i < resultArray.length; i++) { - PostAggregatedDataPoints result = new PostAggregatedDataPoints(inputPoints[i], - dps[i]); - - String alias = aliasTemplate; - for (Map.Entry e : inputPoints[i].getTags().entrySet()) { - alias = alias.replace("@" + e.getKey(), e.getValue()); - } - - result.setAlias(alias); - resultArray[i] = result; - } + static Joiner COMMA_JOINER = Joiner.on(',').skipNulls(); - return resultArray; + @Override + public DataPoints[] evaluate(final TSQuery data_query, + final List query_results, final List params) { + if (data_query == null) { + throw new IllegalArgumentException("Missing time series query"); } - - @Override - public String writeStringField(List queryParams, String innerExpression) { - if (queryParams == null || queryParams.size() == 0) { - return "NULL"; + if (query_results == null || query_results.isEmpty()) { + return new DataPoints[]{}; + } + if (params == null || params.isEmpty()) { + throw new IllegalArgumentException("Missing the alias"); + } + final String alias_template = COMMA_JOINER.join(params); + + int num_results = 0; + for (DataPoints[] results: query_results) { + num_results += results.length; + } + + final DataPoints[] results = new DataPoints[num_results]; + int ix = 0; + // one or more sub queries (m=...&m=...&m=...) + for (final DataPoints[] sub_query_result : query_results) { + // group bys (m=sum:foo{host=*}) + for (final DataPoints dps : sub_query_result) { + // TODO(cl) - Using an array as the size function may not return the exact + // results and we should figure a way to avoid copying data anyway. + final List new_dps_list = new ArrayList(); + final SeekableView view = dps.iterator(); + while (view.hasNext()) { + DataPoint pt = view.next(); + if (pt.isInteger()) { + new_dps_list.add(MutableDataPoint.ofLongValue( + pt.timestamp(), Math.abs(pt.longValue()))); + } else { + new_dps_list.add(MutableDataPoint.ofDoubleValue( + pt.timestamp(), Math.abs(pt.doubleValue()))); + } } - - return queryParams.get(0); + + final DataPoint[] new_dps = new DataPoint[dps.size()]; + new_dps_list.toArray(new_dps); + final PostAggregatedDataPoints padps = new PostAggregatedDataPoints( + dps, new_dps); + + padps.setAlias(alias_template); + results[ix++] = padps; + } } + return results; + } + + @Override + public String writeStringField(final List query_params, + final String innerExpression) { + final StringBuilder buf = new StringBuilder(); + buf.append("alias(") + .append(innerExpression) + .append(query_params == null || query_params.isEmpty() + ? "" : "," + COMMA_JOINER.join(query_params)) + .append(")"); + return buf.toString(); + } } \ No newline at end of file diff --git a/src/query/expression/PostAggregatedDataPoints.java b/src/query/expression/PostAggregatedDataPoints.java index e0787f62fd..2af4bf47ff 100644 --- a/src/query/expression/PostAggregatedDataPoints.java +++ b/src/query/expression/PostAggregatedDataPoints.java @@ -15,6 +15,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.NoSuchElementException; import org.hbase.async.Bytes.ByteMap; @@ -24,6 +25,7 @@ import net.opentsdb.core.SeekableView; import net.opentsdb.meta.Annotation; +import com.stumbleupon.async.Callback; import com.stumbleupon.async.Deferred; /** @@ -61,16 +63,34 @@ public PostAggregatedDataPoints(final DataPoints base_data_points, @Override public String metricName() { - if (alias != null) { - return alias; - } else { - return base_data_points.metricName(); + try { + return metricNameAsync().join(); + } catch (Exception e) { + throw new RuntimeException("Unexpected exception waiting for " + + "name resolution", e); } } @Override public Deferred metricNameAsync() { if (alias != null) { + if (alias.contains("@") && getTagUids().size() > 0) { + // need to resolve the tag UIDs for the templating feature + + class TemplateFill implements Callback, + Map> { + @Override + public Deferred call(final Map tags) + throws Exception { + for (final Entry pair : tags.entrySet()) { + alias = alias.replace("@" + pair.getKey(), pair.getValue()); + } + return Deferred.fromResult(alias); + } + } + return base_data_points.getTagsAsync() + .addCallbackDeferring(new TemplateFill()); + } return Deferred.fromResult(alias); } return base_data_points.metricNameAsync(); diff --git a/test/query/expression/TestAlias.java b/test/query/expression/TestAlias.java new file mode 100644 index 0000000000..744287e50f --- /dev/null +++ b/test/query/expression/TestAlias.java @@ -0,0 +1,321 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query.expression; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import net.opentsdb.core.DataPoint; +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.SeekableView; +import net.opentsdb.core.SeekableViewsForTest; +import net.opentsdb.core.TSQuery; + +import org.hbase.async.Bytes.ByteMap; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import com.stumbleupon.async.Deferred; + +@RunWith(PowerMockRunner.class) +@PowerMockIgnore({"javax.management.*", "javax.xml.*", + "ch.qos.*", "org.slf4j.*", + "com.sum.*", "org.xml.*"}) +@PrepareForTest({ TSQuery.class }) +public class TestAlias { + + private static long START_TIME = 1356998400000L; + private static int INTERVAL = 60000; + private static int NUM_POINTS = 5; + private static String METRIC = "sys.cpu"; + + private TSQuery data_query; + private SeekableView view; + private DataPoints dps; + private DataPoints[] group_bys; + private List query_results; + private List params; + private Alias func; + private Map tags; + private ByteMap tag_uids; + + @Before + public void before() throws Exception { + view = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 1, 1); + data_query = mock(TSQuery.class); + when(data_query.startTime()).thenReturn(START_TIME); + when(data_query.endTime()).thenReturn(START_TIME + (INTERVAL * NUM_POINTS)); + + tags = new HashMap(2); + tags.put("host", "web01"); + tags.put("dc", "lga"); + tag_uids = new ByteMap(); + tag_uids.put(new byte[] { 0, 0, 1 }, new byte[] { 0, 0, 1 }); + tag_uids.put(new byte[] { 0, 0, 2 }, new byte[] { 0, 0, 2 }); + + dps = PowerMockito.mock(DataPoints.class); + when(dps.iterator()).thenReturn(view); + when(dps.metricName()).thenReturn(METRIC); + when(dps.getTagsAsync()).thenReturn(Deferred.fromResult(tags)); + when(dps.getTagUids()).thenReturn(tag_uids); + + group_bys = new DataPoints[] { dps }; + + query_results = new ArrayList(1); + query_results.add(group_bys); + + params = new ArrayList(1); + func = new Alias(); + } + + @Test + public void evaluateGroupByLong() throws Exception { + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + group_bys = new DataPoints[] { dps, dps2 }; + query_results.clear(); + query_results.add(group_bys); + params.add("My Alias"); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(2, results.length); + assertEquals("My Alias", results[0].metricName()); + assertEquals("My Alias", results[1].metricName()); + + long ts = START_TIME; + long v = 1; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + ts = START_TIME; + v = 10; + for (DataPoint dp : results[1]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + } + + @Test + public void evaluateGroupByDouble() throws Exception { + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, false, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + group_bys = new DataPoints[] { dps, dps2 }; + query_results.clear(); + query_results.add(group_bys); + params.add("My Alias"); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(2, results.length); + assertEquals("My Alias", results[0].metricName()); + assertEquals("My Alias", results[1].metricName()); + + long ts = START_TIME; + double v = 1; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals((long)v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + ts = START_TIME; + v = 10; + for (DataPoint dp : results[1]) { + assertEquals(ts, dp.timestamp()); + assertFalse(dp.isInteger()); + assertEquals(v, dp.doubleValue(), 0.001); + ts += INTERVAL; + v += 1; + } + } + + @Test + public void evaluateSubQuerySeries() throws Exception { + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, -10, -1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + group_bys = new DataPoints[] { dps, dps2 }; + query_results.clear(); + query_results.add(group_bys); + params.add("My Alias"); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(2, results.length); + assertEquals("My Alias", results[0].metricName()); + assertEquals("My Alias", results[1].metricName()); + + long ts = START_TIME; + long v = 1; + for (DataPoint dp : results[0]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + ts = START_TIME; + v = 10; + for (DataPoint dp : results[1]) { + assertEquals(ts, dp.timestamp()); + assertTrue(dp.isInteger()); + assertEquals(v, dp.longValue()); + ts += INTERVAL; + v += 1; + } + } + + @Test + public void evaluateWithTags() throws Exception { + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.getTagsAsync()).thenReturn(Deferred.fromResult(tags)); + when(dps2.getTagUids()).thenReturn(tag_uids); + group_bys = new DataPoints[] { dps, dps2 }; + query_results.clear(); + query_results.add(group_bys); + params.add("My Alias.@host.@dc"); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(2, results.length); + assertEquals("My Alias.web01.lga", results[0].metricName()); + assertEquals("My Alias.web01.lga", results[1].metricName()); + } + + @Test + public void evaluateWithATag() throws Exception { + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.getTagsAsync()).thenReturn(Deferred.fromResult(tags)); + when(dps2.getTagUids()).thenReturn(tag_uids); + group_bys = new DataPoints[] { dps, dps2 }; + query_results.clear(); + query_results.add(group_bys); + params.add("My Alias.@dc"); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(2, results.length); + assertEquals("My Alias.lga", results[0].metricName()); + assertEquals("My Alias.lga", results[1].metricName()); + } + + @Test + public void evaluateWithTagsJoined() throws Exception { + SeekableView view2 = SeekableViewsForTest.generator(START_TIME, INTERVAL, + NUM_POINTS, true, 10, 1); + DataPoints dps2 = PowerMockito.mock(DataPoints.class); + when(dps2.iterator()).thenReturn(view2); + when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.getTagsAsync()).thenReturn(Deferred.fromResult(tags)); + when(dps2.getTagUids()).thenReturn(tag_uids); + group_bys = new DataPoints[] { dps, dps2 }; + query_results.clear(); + query_results.add(group_bys); + params.add("My Alias"); + params.add("@host"); + params.add("@dc"); + params.add("@none"); + + final DataPoints[] results = func.evaluate(data_query, query_results, params); + + assertEquals(2, results.length); + assertEquals("My Alias,web01,lga,@none", results[0].metricName()); + assertEquals("My Alias,web01,lga,@none", results[1].metricName()); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateNullQuery() throws Exception { + params.add("1"); + func.evaluate(null, query_results, params); + } + + @Test + public void evaluateNullResults() throws Exception { + params.add("1"); + final DataPoints[] results = func.evaluate(data_query, null, params); + assertEquals(0, results.length); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateNullParams() throws Exception { + func.evaluate(data_query, query_results, null); + } + + @Test + public void evaluateEmptyResults() throws Exception { + final DataPoints[] results = func.evaluate(data_query, + Collections.emptyList(), params); + assertEquals(0, results.length); + } + + @Test (expected = IllegalArgumentException.class) + public void evaluateEmptyParams() throws Exception { + func.evaluate(data_query, query_results, null); + } + + @Test + public void writeStringField() throws Exception { + assertEquals("alias(m)", func.writeStringField(params, "m")); + params.add("MyAlias"); + assertEquals("alias(m,MyAlias)", func.writeStringField(params, "m")); + params.clear(); + params.add("Alias"); + params.add("@host"); + assertEquals("alias(m,Alias,@host)", func.writeStringField(params, "m")); + params.clear(); + assertEquals("alias(null)", func.writeStringField(params, null)); + assertEquals("alias()", func.writeStringField(params, "")); + assertEquals("alias(inner_expression)", + func.writeStringField(null, "inner_expression")); + } +} From 90265791efe4824dba012d485b6e8cea8e844c8e Mon Sep 17 00:00:00 2001 From: Yubao Liu Date: Wed, 25 Nov 2015 00:40:01 +0800 Subject: [PATCH 088/233] "tsdb fsck --fix-all" wrongly clears data points when salting enabled Const.SALT_WIDTH() is actually not constant value, it mustn't be called on class initialization. Signed-off-by: Chris Larsen --- src/tools/Fsck.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/Fsck.java b/src/tools/Fsck.java index 9f006b7b19..247dd9818a 100644 --- a/src/tools/Fsck.java +++ b/src/tools/Fsck.java @@ -114,11 +114,11 @@ final class Fsck { final AtomicLong vle_fixed = new AtomicLong(); /** Length of the metric + timestamp for key validation */ - private static int key_prefix_length = Const.SALT_WIDTH() + + private int key_prefix_length = Const.SALT_WIDTH() + TSDB.metrics_width() + Const.TIMESTAMP_BYTES; /** Length of a tagk + tagv pair for key validation */ - private static int key_tags_length = TSDB.tagk_width() + TSDB.tagv_width(); + private int key_tags_length = TSDB.tagk_width() + TSDB.tagv_width(); /** How often to report progress */ private static long report_rows = 10000; From b5be7f3424f3cb8074c31a4b283f051425e35236 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Fri, 27 Nov 2015 18:50:39 -0800 Subject: [PATCH 089/233] Add code to properly handle append data points in the FSCK. More work can be done but at least it won't throw errors. Signed-off-by: Chris Larsen --- src/tools/Fsck.java | 15 ++++++ test/tools/TestFsck.java | 114 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+) diff --git a/src/tools/Fsck.java b/src/tools/Fsck.java index 247dd9818a..45dde4539f 100644 --- a/src/tools/Fsck.java +++ b/src/tools/Fsck.java @@ -34,6 +34,7 @@ import com.stumbleupon.async.Deferred; +import net.opentsdb.core.AppendDataPoints; import net.opentsdb.core.Const; import net.opentsdb.core.IllegalDataException; import net.opentsdb.core.Internal; @@ -92,6 +93,8 @@ final class Fsck { final AtomicLong rows_processed = new AtomicLong(); final AtomicLong valid_datapoints = new AtomicLong(); final AtomicLong annotations = new AtomicLong(); + final AtomicLong append_dps = new AtomicLong(); + final AtomicLong append_dps_fixed = new AtomicLong(); final AtomicLong bad_key = new AtomicLong(); final AtomicLong bad_key_fixed = new AtomicLong(); final AtomicLong duplicates = new AtomicLong(); @@ -382,6 +385,18 @@ private void fsckRow(final ArrayList row, if (qual[0] == Annotation.PREFIX()) { annotations.getAndIncrement(); continue; + } else if (qual[0] == AppendDataPoints.APPEND_COLUMN_PREFIX) { + append_dps.getAndIncrement(); + try { + final AppendDataPoints adps = new AppendDataPoints(); + adps.parseKeyValue(tsdb, kv); + if (adps.repairedDeferred() != null) { + append_dps_fixed.incrementAndGet(); + } + } catch (RuntimeException e) { + LOG.error("Unexpected exception processing append data point: " + kv, e); + } + continue; } LOG.warn("Found an object possibly from a future version of OpenTSDB\n\t" + kv); diff --git a/test/tools/TestFsck.java b/test/tools/TestFsck.java index 58cbd374b9..9eea0aa6a3 100644 --- a/test/tools/TestFsck.java +++ b/test/tools/TestFsck.java @@ -1474,6 +1474,120 @@ public void badCompactTooLongFix() throws Exception { assertEquals(-1, storage.numColumns(ROW)); } + @Test + public void appendOK() throws Exception { + final byte[] qual1 = { 0x0, 0x07 }; + final byte[] val1 = Bytes.fromLong(4L); + final byte[] qual2 = { 0x0, 0x27 }; + final byte[] val2 = Bytes.fromLong(5L); + final byte[] appendq = { 0x05, 0x0, 0x0 }; + storage.addColumn(ROW, appendq, + MockBase.concatByteArrays(qual1, val1, qual2, val2)); + + final Fsck fsck = new Fsck(tsdb, options); + fsck.runFullTable(); + assertEquals(1, fsck.kvs_processed.get()); + assertEquals(0, fsck.bad_compacted_columns.get()); + assertEquals(1, fsck.append_dps.get()); + assertEquals(0, fsck.append_dps_fixed.get()); + assertEquals(0, fsck.totalErrors()); + assertEquals(0, fsck.correctable()); + assertArrayEquals( MockBase.concatByteArrays(qual1, val1, qual2, val2), + storage.getColumn(ROW, appendq)); + } + + @Test + public void appendOutOfOrder() throws Exception { + final byte[] qual1 = { 0x0, 0x07 }; + final byte[] val1 = Bytes.fromLong(4L); + final byte[] qual2 = { 0x0, 0x27 }; + final byte[] val2 = Bytes.fromLong(5L); + final byte[] appendq = { 0x05, 0x0, 0x0 }; + storage.addColumn(ROW, appendq, + MockBase.concatByteArrays(qual2, val2, qual1, val1)); + + final Fsck fsck = new Fsck(tsdb, options); + fsck.runFullTable(); + assertEquals(1, fsck.kvs_processed.get()); + assertEquals(0, fsck.bad_compacted_columns.get()); + assertEquals(1, fsck.append_dps.get()); + assertEquals(0, fsck.append_dps_fixed.get()); + assertEquals(0, fsck.totalErrors()); + assertEquals(0, fsck.correctable()); + assertArrayEquals( MockBase.concatByteArrays(qual2, val2, qual1, val1), + storage.getColumn(ROW, appendq)); + } + + @Test + public void appendOutOfOrderFixed() throws Exception { + config.overrideConfig("tsd.storage.repair_appends", "true"); + final byte[] qual1 = { 0x0, 0x07 }; + final byte[] val1 = Bytes.fromLong(4L); + final byte[] qual2 = { 0x0, 0x27 }; + final byte[] val2 = Bytes.fromLong(5L); + final byte[] appendq = { 0x05, 0x0, 0x0 }; + storage.addColumn(ROW, appendq, + MockBase.concatByteArrays(qual2, val2, qual1, val1)); + + final Fsck fsck = new Fsck(tsdb, options); + fsck.runFullTable(); + assertEquals(1, fsck.kvs_processed.get()); + assertEquals(0, fsck.bad_compacted_columns.get()); + assertEquals(1, fsck.append_dps.get()); + assertEquals(1, fsck.append_dps_fixed.get()); + assertEquals(0, fsck.totalErrors()); + assertEquals(0, fsck.correctable()); + assertArrayEquals( MockBase.concatByteArrays(qual1, val1, qual2, val2), + storage.getColumn(ROW, appendq)); + } + + @Test + public void appendDupe() throws Exception { + final byte[] qual1 = { 0x0, 0x07 }; + final byte[] val1 = Bytes.fromLong(4L); + final byte[] qual2 = { 0x0, 0x27 }; + final byte[] val2 = Bytes.fromLong(5L); + final byte[] appendq = { 0x05, 0x0, 0x0 }; + storage.addColumn(ROW, appendq, + MockBase.concatByteArrays(qual1, val1, qual1, val1, qual2, val2)); + + final Fsck fsck = new Fsck(tsdb, options); + fsck.runFullTable(); + assertEquals(1, fsck.kvs_processed.get()); + assertEquals(0, fsck.bad_compacted_columns.get()); + assertEquals(1, fsck.append_dps.get()); + assertEquals(0, fsck.append_dps_fixed.get()); + assertEquals(0, fsck.totalErrors()); + assertEquals(0, fsck.correctable()); + assertArrayEquals( MockBase.concatByteArrays(qual1, val1, qual1, val1, qual2, val2), + storage.getColumn(ROW, appendq)); + } + /* + * TODO - Fix dupes in the appends by re-writing the data. Right now it just + * resolves them at query time but leaves the values in storage. + @Test + public void appendDupeFix() throws Exception { + final byte[] qual1 = { 0x0, 0x07 }; + final byte[] val1 = Bytes.fromLong(4L); + final byte[] qual2 = { 0x0, 0x27 }; + final byte[] val2 = Bytes.fromLong(5L); + final byte[] appendq = { 0x05, 0x0, 0x0 }; + storage.addColumn(ROW, appendq, + MockBase.concatByteArrays(qual1, val1, qual1, val1, qual2, val2)); + + final Fsck fsck = new Fsck(tsdb, options); + fsck.runFullTable(); + assertEquals(1, fsck.kvs_processed.get()); + assertEquals(0, fsck.bad_compacted_columns.get()); + assertEquals(1, fsck.append_dps.get()); + assertEquals(1, fsck.append_dps_fixed.get()); + assertEquals(0, fsck.totalErrors()); + assertEquals(0, fsck.correctable()); + assertArrayEquals( MockBase.concatByteArrays(qual1, val1, qual2, val2), + storage.getColumn(ROW, appendq)); + } + */ + // VLE -------------------------------------------- @Test From 2c14cd45e524e6540a7632f98009a4a2700d78ab Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Fri, 20 Nov 2015 18:21:37 -0800 Subject: [PATCH 090/233] Fix up Expression method UTs for async metric lookups. Signed-off-by: Chris Larsen --- test/query/expression/TestAbsolute.java | 14 ++++++++------ test/query/expression/TestHighestCurrent.java | 18 ++++++++++-------- test/query/expression/TestHighestMax.java | 16 +++++++++------- test/query/expression/TestMovingAverage.java | 8 +++++--- test/query/expression/TestScale.java | 18 ++++++++++-------- 5 files changed, 42 insertions(+), 32 deletions(-) diff --git a/test/query/expression/TestAbsolute.java b/test/query/expression/TestAbsolute.java index 3d675ee0d6..7d822fe4f4 100644 --- a/test/query/expression/TestAbsolute.java +++ b/test/query/expression/TestAbsolute.java @@ -37,6 +37,8 @@ import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; +import com.stumbleupon.async.Deferred; + @RunWith(PowerMockRunner.class) @PowerMockIgnore({"javax.management.*", "javax.xml.*", "ch.qos.*", "org.slf4j.*", @@ -67,7 +69,7 @@ public void before() throws Exception { dps = PowerMockito.mock(DataPoints.class); when(dps.iterator()).thenReturn(view); - when(dps.metricName()).thenReturn(METRIC); + when(dps.metricNameAsync()).thenReturn(Deferred.fromResult(METRIC)); group_bys = new DataPoints[] { dps }; @@ -84,7 +86,7 @@ public void evaluatePositiveGroupByLong() throws Exception { NUM_POINTS, true, 10, 1); DataPoints dps2 = PowerMockito.mock(DataPoints.class); when(dps2.iterator()).thenReturn(view2); - when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricNameAsync()).thenReturn(Deferred.fromResult("sys.mem")); group_bys = new DataPoints[] { dps, dps2 }; query_results.clear(); query_results.add(group_bys); @@ -121,7 +123,7 @@ public void evaluatePositiveGroupByDouble() throws Exception { NUM_POINTS, false, 10, 1); DataPoints dps2 = PowerMockito.mock(DataPoints.class); when(dps2.iterator()).thenReturn(view2); - when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricNameAsync()).thenReturn(Deferred.fromResult("sys.mem")); group_bys = new DataPoints[] { dps, dps2 }; query_results.clear(); query_results.add(group_bys); @@ -158,7 +160,7 @@ public void evaluateFactorNegativeGroupByLong() throws Exception { NUM_POINTS, true, -10, -1); DataPoints dps2 = PowerMockito.mock(DataPoints.class); when(dps2.iterator()).thenReturn(view2); - when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricNameAsync()).thenReturn(Deferred.fromResult("sys.mem")); group_bys = new DataPoints[] { dps, dps2 }; query_results.clear(); query_results.add(group_bys); @@ -195,7 +197,7 @@ public void evaluateNegativeGroupByDouble() throws Exception { NUM_POINTS, false, -10, -1); DataPoints dps2 = PowerMockito.mock(DataPoints.class); when(dps2.iterator()).thenReturn(view2); - when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricNameAsync()).thenReturn(Deferred.fromResult("sys.mem")); group_bys = new DataPoints[] { dps, dps2 }; query_results.clear(); query_results.add(group_bys); @@ -233,7 +235,7 @@ public void evaluateNegativeSubQuerySeries() throws Exception { NUM_POINTS, true, -10, -1); DataPoints dps2 = PowerMockito.mock(DataPoints.class); when(dps2.iterator()).thenReturn(view2); - when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricNameAsync()).thenReturn(Deferred.fromResult("sys.mem")); group_bys = new DataPoints[] { dps, dps2 }; query_results.clear(); query_results.add(group_bys); diff --git a/test/query/expression/TestHighestCurrent.java b/test/query/expression/TestHighestCurrent.java index 145c81f41d..876ee6356d 100644 --- a/test/query/expression/TestHighestCurrent.java +++ b/test/query/expression/TestHighestCurrent.java @@ -36,6 +36,8 @@ import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; +import com.stumbleupon.async.Deferred; + @RunWith(PowerMockRunner.class) @PowerMockIgnore({"javax.management.*", "javax.xml.*", "ch.qos.*", "org.slf4j.*", @@ -66,7 +68,7 @@ public void before() throws Exception { dps = PowerMockito.mock(DataPoints.class); when(dps.iterator()).thenReturn(view); - when(dps.metricName()).thenReturn(METRIC); + when(dps.metricNameAsync()).thenReturn(Deferred.fromResult(METRIC)); group_bys = new DataPoints[] { dps }; @@ -84,7 +86,7 @@ public void evaluateTopN1with2SeriesLong() throws Exception { NUM_POINTS, true, 10, 1); DataPoints dps2 = PowerMockito.mock(DataPoints.class); when(dps2.iterator()).thenReturn(view2); - when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricNameAsync()).thenReturn(Deferred.fromResult("sys.mem")); group_bys = new DataPoints[] { dps, dps2 }; query_results.clear(); query_results.add(group_bys); @@ -112,7 +114,7 @@ public void evaluateTopN2with2SeriesLong() throws Exception { NUM_POINTS, true, 10, 1); DataPoints dps2 = PowerMockito.mock(DataPoints.class); when(dps2.iterator()).thenReturn(view2); - when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricNameAsync()).thenReturn(Deferred.fromResult("sys.mem")); group_bys = new DataPoints[] { dps, dps2 }; query_results.clear(); query_results.add(group_bys); @@ -151,7 +153,7 @@ public void evaluateTopN100with2SeriesLong() throws Exception { NUM_POINTS, true, 10, 1); DataPoints dps2 = PowerMockito.mock(DataPoints.class); when(dps2.iterator()).thenReturn(view2); - when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricNameAsync()).thenReturn(Deferred.fromResult("sys.mem")); DataPoints[] group_bys2 = new DataPoints[] { dps2 }; query_results.add(group_bys2); @@ -189,7 +191,7 @@ public void evaluateTopN100with2SubQuerySeriesLong() throws Exception { NUM_POINTS, true, 10, 1); DataPoints dps2 = PowerMockito.mock(DataPoints.class); when(dps2.iterator()).thenReturn(view2); - when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricNameAsync()).thenReturn(Deferred.fromResult("sys.mem")); group_bys = new DataPoints[] { dps, dps2 }; query_results.clear(); query_results.add(group_bys); @@ -228,7 +230,7 @@ public void evaluateTopN2with2SeriesDouble() throws Exception { NUM_POINTS, false, 10, 1.5); DataPoints dps2 = PowerMockito.mock(DataPoints.class); when(dps2.iterator()).thenReturn(view2); - when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricNameAsync()).thenReturn(Deferred.fromResult("sys.mem")); group_bys = new DataPoints[] { dps, dps2 }; query_results.clear(); query_results.add(group_bys); @@ -267,7 +269,7 @@ public void evaluateTopN1with2SeriesLongDoubleMixed() throws Exception { NUM_POINTS, false, 10, 1.5, true); DataPoints dps2 = PowerMockito.mock(DataPoints.class); when(dps2.iterator()).thenReturn(view2); - when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricNameAsync()).thenReturn(Deferred.fromResult("sys.mem")); group_bys = new DataPoints[] { dps, dps2 }; query_results.clear(); query_results.add(group_bys); @@ -304,7 +306,7 @@ public void evaluateTopN1with2SeriesDiffSpan() throws Exception { 3, true, 10, 1); DataPoints dps2 = PowerMockito.mock(DataPoints.class); when(dps2.iterator()).thenReturn(view2); - when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricNameAsync()).thenReturn(Deferred.fromResult("sys.mem")); group_bys = new DataPoints[] { dps, dps2 }; query_results.clear(); query_results.add(group_bys); diff --git a/test/query/expression/TestHighestMax.java b/test/query/expression/TestHighestMax.java index 2426204bc5..66e38689a7 100644 --- a/test/query/expression/TestHighestMax.java +++ b/test/query/expression/TestHighestMax.java @@ -36,6 +36,8 @@ import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; +import com.stumbleupon.async.Deferred; + @RunWith(PowerMockRunner.class) @PowerMockIgnore({"javax.management.*", "javax.xml.*", "ch.qos.*", "org.slf4j.*", @@ -66,7 +68,7 @@ public void before() throws Exception { dps = PowerMockito.mock(DataPoints.class); when(dps.iterator()).thenReturn(view); - when(dps.metricName()).thenReturn(METRIC); + when(dps.metricNameAsync()).thenReturn(Deferred.fromResult(METRIC)); group_bys = new DataPoints[] { dps }; @@ -84,7 +86,7 @@ public void evaluateTopN1with2SeriesLong() throws Exception { NUM_POINTS, true, 10, 1); DataPoints dps2 = PowerMockito.mock(DataPoints.class); when(dps2.iterator()).thenReturn(view2); - when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricNameAsync()).thenReturn(Deferred.fromResult("sys.mem")); group_bys = new DataPoints[] { dps, dps2 }; query_results.clear(); query_results.add(group_bys); @@ -112,7 +114,7 @@ public void evaluateTopN2with2SeriesLong() throws Exception { NUM_POINTS, true, 10, 1); DataPoints dps2 = PowerMockito.mock(DataPoints.class); when(dps2.iterator()).thenReturn(view2); - when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricNameAsync()).thenReturn(Deferred.fromResult("sys.mem")); group_bys = new DataPoints[] { dps, dps2 }; query_results.clear(); query_results.add(group_bys); @@ -151,7 +153,7 @@ public void evaluateTopN100with2SeriesLong() throws Exception { NUM_POINTS, true, 10, 1); DataPoints dps2 = PowerMockito.mock(DataPoints.class); when(dps2.iterator()).thenReturn(view2); - when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricNameAsync()).thenReturn(Deferred.fromResult("sys.mem")); DataPoints[] group_bys2 = new DataPoints[] { dps2 }; query_results.add(group_bys2); @@ -189,7 +191,7 @@ public void evaluateTopN100with2SubQuerySeriesLong() throws Exception { NUM_POINTS, true, 10, 1); DataPoints dps2 = PowerMockito.mock(DataPoints.class); when(dps2.iterator()).thenReturn(view2); - when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricNameAsync()).thenReturn(Deferred.fromResult("sys.mem")); group_bys = new DataPoints[] { dps, dps2 }; query_results.clear(); query_results.add(group_bys); @@ -228,7 +230,7 @@ public void evaluateTopN2with2SeriesDouble() throws Exception { NUM_POINTS, false, 10, 1.5); DataPoints dps2 = PowerMockito.mock(DataPoints.class); when(dps2.iterator()).thenReturn(view2); - when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricNameAsync()).thenReturn(Deferred.fromResult("sys.mem")); group_bys = new DataPoints[] { dps, dps2 }; query_results.clear(); query_results.add(group_bys); @@ -267,7 +269,7 @@ public void evaluateTopN1with2SeriesLongDoubleMixed() throws Exception { NUM_POINTS, false, 10, 1.5, true); DataPoints dps2 = PowerMockito.mock(DataPoints.class); when(dps2.iterator()).thenReturn(view2); - when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricNameAsync()).thenReturn(Deferred.fromResult("sys.mem")); group_bys = new DataPoints[] { dps, dps2 }; query_results.clear(); query_results.add(group_bys); diff --git a/test/query/expression/TestMovingAverage.java b/test/query/expression/TestMovingAverage.java index 4a59cf38a1..3d2be90d99 100644 --- a/test/query/expression/TestMovingAverage.java +++ b/test/query/expression/TestMovingAverage.java @@ -36,6 +36,8 @@ import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; +import com.stumbleupon.async.Deferred; + @RunWith(PowerMockRunner.class) @PowerMockIgnore({"javax.management.*", "javax.xml.*", "ch.qos.*", "org.slf4j.*", @@ -65,7 +67,7 @@ public void before() throws Exception { dps = PowerMockito.mock(DataPoints.class); when(dps.iterator()).thenReturn(view); - when(dps.metricName()).thenReturn(METRIC); + when(dps.metricNameAsync()).thenReturn(Deferred.fromResult(METRIC)); group_bys = new DataPoints[] { dps }; @@ -282,7 +284,7 @@ public void evaluateGroupBy() throws Exception { NUM_POINTS, true, 10, 1); DataPoints dps2 = PowerMockito.mock(DataPoints.class); when(dps2.iterator()).thenReturn(view2); - when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricNameAsync()).thenReturn(Deferred.fromResult("sys.mem")); group_bys = new DataPoints[] { dps, dps2 }; query_results.clear(); query_results.add(group_bys); @@ -322,7 +324,7 @@ public void evaluateSubQuery() throws Exception { NUM_POINTS, true, 10, 1); DataPoints dps2 = PowerMockito.mock(DataPoints.class); when(dps2.iterator()).thenReturn(view2); - when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricNameAsync()).thenReturn(Deferred.fromResult("sys.mem")); DataPoints[] group_bys2 = new DataPoints[] { dps2 }; query_results.add(group_bys2); diff --git a/test/query/expression/TestScale.java b/test/query/expression/TestScale.java index f7f35dd687..d34469d7c0 100644 --- a/test/query/expression/TestScale.java +++ b/test/query/expression/TestScale.java @@ -36,6 +36,8 @@ import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; +import com.stumbleupon.async.Deferred; + @RunWith(PowerMockRunner.class) @PowerMockIgnore({"javax.management.*", "javax.xml.*", "ch.qos.*", "org.slf4j.*", @@ -66,7 +68,7 @@ public void before() throws Exception { dps = PowerMockito.mock(DataPoints.class); when(dps.iterator()).thenReturn(view); - when(dps.metricName()).thenReturn(METRIC); + when(dps.metricNameAsync()).thenReturn(Deferred.fromResult(METRIC)); group_bys = new DataPoints[] { dps }; @@ -84,7 +86,7 @@ public void evaluateFactor1GroupByLong() throws Exception { NUM_POINTS, true, 10, 1); DataPoints dps2 = PowerMockito.mock(DataPoints.class); when(dps2.iterator()).thenReturn(view2); - when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricNameAsync()).thenReturn(Deferred.fromResult("sys.mem")); group_bys = new DataPoints[] { dps, dps2 }; query_results.clear(); query_results.add(group_bys); @@ -122,7 +124,7 @@ public void evaluateFactor1GroupByDouble() throws Exception { NUM_POINTS, false, 10, 1.5); DataPoints dps2 = PowerMockito.mock(DataPoints.class); when(dps2.iterator()).thenReturn(view2); - when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricNameAsync()).thenReturn(Deferred.fromResult("sys.mem")); group_bys = new DataPoints[] { dps, dps2 }; query_results.clear(); query_results.add(group_bys); @@ -160,7 +162,7 @@ public void evaluateFactor1point5GroupBy() throws Exception { NUM_POINTS, true, 10, 1); DataPoints dps2 = PowerMockito.mock(DataPoints.class); when(dps2.iterator()).thenReturn(view2); - when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricNameAsync()).thenReturn(Deferred.fromResult("sys.mem")); group_bys = new DataPoints[] { dps, dps2 }; query_results.clear(); query_results.add(group_bys); @@ -199,7 +201,7 @@ public void evaluateFactor1024GroupBy() throws Exception { NUM_POINTS, true, 10, 1); DataPoints dps2 = PowerMockito.mock(DataPoints.class); when(dps2.iterator()).thenReturn(view2); - when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricNameAsync()).thenReturn(Deferred.fromResult("sys.mem")); group_bys = new DataPoints[] { dps, dps2 }; query_results.clear(); query_results.add(group_bys); @@ -237,7 +239,7 @@ public void evaluateFactor1SubQuerySeries() throws Exception { NUM_POINTS, true, 10, 1); DataPoints dps2 = PowerMockito.mock(DataPoints.class); when(dps2.iterator()).thenReturn(view2); - when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricNameAsync()).thenReturn(Deferred.fromResult("sys.mem")); group_bys = new DataPoints[] { dps, dps2 }; query_results.clear(); query_results.add(group_bys); @@ -275,7 +277,7 @@ public void evaluateFactor0GroupByLong() throws Exception { NUM_POINTS, true, 10, 1); DataPoints dps2 = PowerMockito.mock(DataPoints.class); when(dps2.iterator()).thenReturn(view2); - when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricNameAsync()).thenReturn(Deferred.fromResult("sys.mem")); group_bys = new DataPoints[] { dps, dps2 }; query_results.clear(); query_results.add(group_bys); @@ -309,7 +311,7 @@ public void evaluateFactorNegative1GroupByLong() throws Exception { NUM_POINTS, true, 10, 1); DataPoints dps2 = PowerMockito.mock(DataPoints.class); when(dps2.iterator()).thenReturn(view2); - when(dps2.metricName()).thenReturn("sys.mem"); + when(dps2.metricNameAsync()).thenReturn(Deferred.fromResult("sys.mem")); group_bys = new DataPoints[] { dps, dps2 }; query_results.clear(); query_results.add(group_bys); From 73cd9cda87781ba8abb4f5030242d308e702b1d7 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Fri, 20 Nov 2015 17:22:10 -0800 Subject: [PATCH 091/233] Fix for #639 thanks to @rluta! Signed-off-by: Chris Larsen --- src/tools/UidManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/UidManager.java b/src/tools/UidManager.java index fc51dd6287..5dccc5837e 100644 --- a/src/tools/UidManager.java +++ b/src/tools/UidManager.java @@ -361,7 +361,7 @@ private static int assign(final TSDB tsdb, final short idwidth, final String[] args) { boolean randomize = false; - if (UniqueIdType.valueOf(args[1]) == UniqueIdType.METRIC) { + if (UniqueId.stringToUniqueIdType(args[1]) == UniqueIdType.METRIC) { randomize = tsdb.getConfig().getBoolean("tsd.core.uid.random_metrics"); } final UniqueId uid = new UniqueId(tsdb.getClient(), table, args[1], From a997aded698778164d1be8f52cf2ffd13edf276a Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sun, 29 Nov 2015 12:55:10 -0800 Subject: [PATCH 092/233] Fix the IncomingDataPoints class for the text importer CLI where, with salting enabled, the row key was corrupted as it wrote the timestamp in the wrong position. Fixes #623. Thanks @rgazaryants Signed-off-by: Chris Larsen --- src/core/IncomingDataPoints.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/core/IncomingDataPoints.java b/src/core/IncomingDataPoints.java index 4c98e3a596..6fb9c9ba23 100644 --- a/src/core/IncomingDataPoints.java +++ b/src/core/IncomingDataPoints.java @@ -156,8 +156,8 @@ static Deferred rowKeyTemplateAsync(final TSDB tsdb, final short tag_value_width = tsdb.tag_values.width(); final short num_tags = (short) tags.size(); - int row_size = (metric_width + Const.TIMESTAMP_BYTES + tag_name_width - * num_tags + tag_value_width * num_tags); + int row_size = (Const.SALT_WIDTH() + metric_width + Const.TIMESTAMP_BYTES + + tag_name_width * num_tags + tag_value_width * num_tags); final byte[] row = new byte[row_size]; // Lookup or create the metric ID. @@ -171,7 +171,7 @@ static Deferred rowKeyTemplateAsync(final TSDB tsdb, // Copy the metric ID at the beginning of the row key. class CopyMetricInRowKeyCB implements Callback { public byte[] call(final byte[] metricid) { - copyInRowKey(row, (short) 0, metricid); + copyInRowKey(row, (short) Const.SALT_WIDTH(), metricid); return row; } } @@ -180,7 +180,7 @@ public byte[] call(final byte[] metricid) { class CopyTagsInRowKeyCB implements Callback, ArrayList> { public Deferred call(final ArrayList tags) { - short pos = metric_width; + short pos = (short) (Const.SALT_WIDTH() + metric_width); pos += Const.TIMESTAMP_BYTES; for (final byte[] tag : tags) { copyInRowKey(row, pos, tag); @@ -242,7 +242,7 @@ private long updateBaseTime(final long timestamp) { // because the HBase client may still hold a reference to it in its // internal datastructures. row = Arrays.copyOf(row, row.length); - Bytes.setInt(row, (int) base_time, tsdb.metrics.width()); + Bytes.setInt(row, (int) base_time, Const.SALT_WIDTH() + tsdb.metrics.width()); RowKey.prefixKeyWithSalt(row); // in case the timestamp will be involved in // salting later tsdb.scheduleForCompaction(row, (int) base_time); From 31332d7077193010584c613241d000f679f7ffda Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sun, 29 Nov 2015 13:17:06 -0800 Subject: [PATCH 093/233] Output the index of the query with the results Signed-off-by: Chris Larsen --- src/core/TSQuery.java | 2 ++ src/core/TSSubQuery.java | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/core/TSQuery.java b/src/core/TSQuery.java index 5310d9a4cf..ccdd48abf2 100644 --- a/src/core/TSQuery.java +++ b/src/core/TSQuery.java @@ -174,8 +174,10 @@ public void validateAndSetQuery() { } // validate queries + int i = 0; for (TSSubQuery sub : queries) { sub.validateAndSetQuery(); + sub.setIndex(i++); } } diff --git a/src/core/TSSubQuery.java b/src/core/TSSubQuery.java index faae314fca..a2aee06e07 100644 --- a/src/core/TSSubQuery.java +++ b/src/core/TSSubQuery.java @@ -71,6 +71,9 @@ public final class TSSubQuery { * tags map. In the future we'll have special JSON objects for them. */ private List filters; + /** Index of the sub query */ + private int index; + /** * Default constructor necessary for POJO de/serialization */ @@ -289,6 +292,12 @@ public ByteSet getFilterTagKs() { return tagks; } + /** @return the index of the sub query + * @since 2.3 */ + public int getIndex() { + return index; + } + /** @param aggregator the name of an aggregation function */ public void setAggregator(String aggregator) { this.aggregator = aggregator; @@ -337,4 +346,10 @@ public void setFilters(List filters) { this.filters = filters; } + /** @param index the index of the sub query + * @since 2.3 */ + public void setIndex(final int index) { + this.index = index; + } + } From 538f7ee5461083f265bd1f1f320481e84aed204e Mon Sep 17 00:00:00 2001 From: Clement Laforet Date: Sat, 28 Nov 2015 23:54:32 +0100 Subject: [PATCH 094/233] - Update asynccassandra to fix build Signed-off-by: Chris Larsen --- ...ndra-0.0.1-20151102.192826-2-jar-with-dependencies.jar.md5 | 1 - ...ndra-0.0.1-20151104.191228-3-jar-with-dependencies.jar.md5 | 1 + third_party/asynccassandra/include.mk | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 third_party/asynccassandra/asynccassandra-0.0.1-20151102.192826-2-jar-with-dependencies.jar.md5 create mode 100644 third_party/asynccassandra/asynccassandra-0.0.1-20151104.191228-3-jar-with-dependencies.jar.md5 diff --git a/third_party/asynccassandra/asynccassandra-0.0.1-20151102.192826-2-jar-with-dependencies.jar.md5 b/third_party/asynccassandra/asynccassandra-0.0.1-20151102.192826-2-jar-with-dependencies.jar.md5 deleted file mode 100644 index 10cf1edf3e..0000000000 --- a/third_party/asynccassandra/asynccassandra-0.0.1-20151102.192826-2-jar-with-dependencies.jar.md5 +++ /dev/null @@ -1 +0,0 @@ -cce1a4b5736fcdcc3ced33982c3069d1 \ No newline at end of file diff --git a/third_party/asynccassandra/asynccassandra-0.0.1-20151104.191228-3-jar-with-dependencies.jar.md5 b/third_party/asynccassandra/asynccassandra-0.0.1-20151104.191228-3-jar-with-dependencies.jar.md5 new file mode 100644 index 0000000000..6469b18da4 --- /dev/null +++ b/third_party/asynccassandra/asynccassandra-0.0.1-20151104.191228-3-jar-with-dependencies.jar.md5 @@ -0,0 +1 @@ +0dd29195cdb9ca4467d0fc32bfffb98c \ No newline at end of file diff --git a/third_party/asynccassandra/include.mk b/third_party/asynccassandra/include.mk index 4d8658a262..7eb8f99e96 100644 --- a/third_party/asynccassandra/include.mk +++ b/third_party/asynccassandra/include.mk @@ -13,11 +13,11 @@ # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see . -ASYNCCASSANDRA_VERSION := 0.0.1-20151102.192826-2 +ASYNCCASSANDRA_VERSION := 0.0.1-20151104.191228-3 ASYNCCASSANDRA := third_party/asynccassandra/asynccassandra-$(ASYNCCASSANDRA_VERSION)-jar-with-dependencies.jar ASYNCCASSANDRA_BASE_URL := https://oss.sonatype.org/content/repositories/snapshots/net/opentsdb/asynccassandra/0.0.1-SNAPSHOT/ $(ASYNCCASSANDRA): $(ASYNCCASSANDRA).md5 set dummy "$(ASYNCCASSANDRA_BASE_URL)" "$(ASYNCCASSANDRA)"; shift; $(FETCH_DEPENDENCY) -THIRD_PARTY += $(ASYNCCASSANDRA) \ No newline at end of file +THIRD_PARTY += $(ASYNCCASSANDRA) From 6e705d867b10beaa425ccb121dd9a34288c3130b Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Wed, 9 Dec 2015 19:21:56 -0800 Subject: [PATCH 095/233] Attempt a fix at #612 by returning a copy of the list of filters in the sub query to avoid hash issues. Signed-off-by: Chris Larsen --- src/core/TSSubQuery.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/TSSubQuery.java b/src/core/TSSubQuery.java index a2aee06e07..8ed1c23bce 100644 --- a/src/core/TSSubQuery.java +++ b/src/core/TSSubQuery.java @@ -274,7 +274,8 @@ public List getFilters() { if (filters == null) { filters = new ArrayList(); } - return filters; + // send a copy so ordering doesn't mess up the hash code + return new ArrayList(filters); } /** @return the unique set of tagks from the filters. May be null if no filters From 98b2b79f98cbfb63479e3ee22627019baf60f1fe Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Mon, 14 Dec 2015 21:18:47 -0800 Subject: [PATCH 096/233] Fix #642 by sorting the tags properly on the bytes, NOT the string values. Sheesh. Thanks @wuxuehong214 Signed-off-by: Chris Larsen --- src/tsd/UniqueIdRpc.java | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/tsd/UniqueIdRpc.java b/src/tsd/UniqueIdRpc.java index 5f49d0af6c..b1c2ddaf67 100644 --- a/src/tsd/UniqueIdRpc.java +++ b/src/tsd/UniqueIdRpc.java @@ -22,6 +22,7 @@ import java.util.TreeMap; import org.hbase.async.Bytes; +import org.hbase.async.Bytes.ByteMap; import org.hbase.async.PutRequest; import org.jboss.netty.handler.codec.http.HttpMethod; import org.jboss.netty.handler.codec.http.HttpResponseStatus; @@ -549,6 +550,7 @@ private TSMeta parseTSMetaQS(final HttpQuery query) { * @param data_query The query we're building * @throws BadRequestException if we are unable to parse the query or it is * missing components + * @todo - make this asynchronous */ private String getTSUIDForMetric(final String query_string, TSDB tsdb) { if (query_string == null || query_string.isEmpty()) { @@ -565,17 +567,23 @@ private String getTSUIDForMetric(final String query_string, TSDB tsdb) { } catch (IllegalArgumentException e) { throw new BadRequestException(e); } - final TreeMap sortedTags = new TreeMap(tags); + + // sort the UIDs on tagk values + final ByteMap tag_uids = new ByteMap(); + for (final Entry pair : tags.entrySet()) { + tag_uids.put(tsdb.getUID(UniqueIdType.TAGK, pair.getKey()), + tsdb.getUID(UniqueIdType.TAGV, pair.getValue())); + } + // Byte Buffer to generate TSUID, pre allocated to the size of the TSUID final ByteArrayOutputStream buf = new ByteArrayOutputStream( - TSDB.metrics_width() + sortedTags.size() * + TSDB.metrics_width() + tag_uids.size() * (TSDB.tagk_width() + TSDB.tagv_width())); try { - buf.write(tsdb.getUID(UniqueIdType.METRIC, metric)); - for (Entry e: sortedTags.entrySet()) { - // Fix for net.opentsdb.tsd.TestUniqueIdRpc.tsuidPostByM() - buf.write(tsdb.getUID(UniqueIdType.TAGK, e.getKey()), 0, TSDB.tagk_width()); - buf.write(tsdb.getUID(UniqueIdType.TAGV, e.getValue()), 0, TSDB.tagv_width()); + buf.write(tsdb.getUID(UniqueIdType.METRIC, metric)); + for (final Entry uids: tag_uids.entrySet()) { + buf.write(uids.getKey()); + buf.write(uids.getValue()); } } catch (IOException e) { throw new BadRequestException(e); From cc1fcd9c73e8328c735acee3643330c541358493 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Mon, 14 Dec 2015 20:13:15 -0800 Subject: [PATCH 097/233] A couple of additional UTs for TSSubQuery Signed-off-by: Chris Larsen --- test/core/TestTSSubQuery.java | 41 +++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/test/core/TestTSSubQuery.java b/test/core/TestTSSubQuery.java index 1b6ac0216e..675dc1ceb1 100644 --- a/test/core/TestTSSubQuery.java +++ b/test/core/TestTSSubQuery.java @@ -188,16 +188,43 @@ public void validateWithGroupByFilter() { } @Test - public void getFilterTagks() { - final TagVFilter filter = TagVFilter.Builder() - .setFilter("*nari").setType("wildcard").setTagk("host").build(); - Whitebox.setInternalState(filter, "tagk_bytes", new byte[] { 0, 0, 1 }); + public void validateWithFilterAndGroupByFilter() { TSSubQuery sub = getMetricForValidate(); - sub.setFilters(Arrays.asList(filter)); + final List filters = new ArrayList(1); + filters.add(new TagVWildcardFilter("colo", "lga*")); + sub.setFilters(filters); + Map tags = new HashMap(); + tags.put("host", TagVWildcardFilter.FILTER_NAME + "(*nari)"); + sub.setTags(tags); + sub.validateAndSetQuery(); + assertEquals("sys.cpu.0", sub.getMetric()); + assertEquals(TagVWildcardFilter.FILTER_NAME + "(*nari)", + sub.getTags().get("host")); + assertEquals(1, sub.getFilters().size()); + assertEquals(Aggregators.SUM, sub.aggregator()); + assertEquals(Aggregators.AVG, sub.downsampler()); + assertEquals(300000, sub.downsampleInterval()); + } + + @Test + public void validateWithFilterAndGroupByFilterSameTag() { + TSSubQuery sub = getMetricForValidate(); + final List filters = new ArrayList(1); + filters.add(new TagVWildcardFilter("host", "veti*")); + sub.setFilters(filters); + Map tags = new HashMap(); + tags.put("host", TagVWildcardFilter.FILTER_NAME + "(*nari)"); + sub.setTags(tags); - assertEquals(1, sub.getFilterTagKs().size()); - assertArrayEquals(new byte[] { 0, 0, 1 }, sub.getFilterTagKs().iterator().next()); + sub.validateAndSetQuery(); + assertEquals("sys.cpu.0", sub.getMetric()); + assertEquals(TagVWildcardFilter.FILTER_NAME + "(*nari)", + sub.getTags().get("host")); + assertEquals(1, sub.getFilters().size()); + assertEquals(Aggregators.SUM, sub.aggregator()); + assertEquals(Aggregators.AVG, sub.downsampler()); + assertEquals(300000, sub.downsampleInterval()); } // NOTE: Each of the hash and equals tests should make sure that we the code From 99a989fb2b43ef1511dc82e88b4ecf94e8b16e1a Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Tue, 15 Dec 2015 12:49:26 -0800 Subject: [PATCH 098/233] Fix #615 by adding a checkbox to the UI to allow group by or non group by operations. Also modify the URI parsing params to be static in QueryRpc.java so they can be used by the GraphHandler class. Signed-off-by: Chris Larsen --- src/tsd/GraphHandler.java | 74 ++--------------- src/tsd/QueryRpc.java | 12 +-- src/tsd/client/MetricForm.java | 141 ++++++++++++++++++++++++++++++--- 3 files changed, 140 insertions(+), 87 deletions(-) diff --git a/src/tsd/GraphHandler.java b/src/tsd/GraphHandler.java index a69bb7f693..6f02526a3b 100644 --- a/src/tsd/GraphHandler.java +++ b/src/tsd/GraphHandler.java @@ -24,7 +24,6 @@ import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.NoSuchElementException; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadFactory; @@ -39,20 +38,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import net.opentsdb.core.Aggregator; -import net.opentsdb.core.Aggregators; import net.opentsdb.core.Const; import net.opentsdb.core.DataPoint; import net.opentsdb.core.DataPoints; -import net.opentsdb.core.DownsamplingSpecification; import net.opentsdb.core.Query; -import net.opentsdb.core.RateOptions; import net.opentsdb.core.TSDB; -import net.opentsdb.core.Tags; +import net.opentsdb.core.TSQuery; import net.opentsdb.graph.Plot; import net.opentsdb.stats.Histogram; import net.opentsdb.stats.StatsCollector; -import net.opentsdb.uid.NoSuchUniqueName; import net.opentsdb.utils.DateTime; import net.opentsdb.utils.JSON; @@ -857,69 +851,11 @@ private static void printMetricHeader(final PrintWriter writer, final String met * @throws IllegalArgumentException if the metric or tags were malformed. */ private static Query[] parseQuery(final TSDB tsdb, final HttpQuery query) { - final List ms = query.getQueryStringParams("m"); - if (ms == null) { - throw BadRequestException.missingParameter("m"); - } - final Query[] tsdbqueries = new Query[ms.size()]; - int nqueries = 0; - for (final String m : ms) { - // m is of the following forms: - // agg:[interval-agg:][rate[{counter[,[countermax][,resetvalue]]}]:] - // metric[{tag=value,...}] - // Where the parts in square brackets `[' .. `]' are optional. - final String[] parts = Tags.splitString(m, ':'); - int i = parts.length; - if (i < 2 || i > 4) { - throw new BadRequestException("Invalid parameter m=" + m + " (" - + (i < 2 ? "not enough" : "too many") + " :-separated parts)"); - } - final Aggregator agg = getAggregator(parts[0]); - i--; // Move to the last part (the metric name). - final HashMap parsedtags = new HashMap(); - final String metric = Tags.parseWithMetric(parts[i], parsedtags); - final boolean rate = parts[--i].startsWith("rate"); - final RateOptions rate_options = QueryRpc.parseRateOptions(rate, parts[i]); - if (rate) { - i--; // Move to the next part. - } - final Query tsdbquery = tsdb.newQuery(); - try { - tsdbquery.setTimeSeries(metric, parsedtags, agg, rate, rate_options); - } catch (NoSuchUniqueName e) { - throw new BadRequestException(e.getMessage()); - } - // downsampling function & interval. - if (i > 0) { - // downsampler given, so parse it - final DownsamplingSpecification ds_spec = - new DownsamplingSpecification(parts[1]); - - tsdbquery.downsample(ds_spec.getInterval(), ds_spec.getFunction(), - ds_spec.getFillPolicy()); - } else { - // no downsampler - tsdbquery.downsample(1000, agg, - DownsamplingSpecification.DEFAULT_FILL_POLICY); - } - tsdbqueries[nqueries++] = tsdbquery; - } - return tsdbqueries; + final TSQuery q = QueryRpc.parseQuery(tsdb, query); + q.validateAndSetQuery(); + return q.buildQueries(tsdb); } - - /** - * Returns the aggregator with the given name. - * @param name Name of the aggregator to get. - * @throws BadRequestException if there's no aggregator with this name. - */ - private static final Aggregator getAggregator(final String name) { - try { - return Aggregators.get(name); - } catch (NoSuchElementException e) { - throw new BadRequestException("No such aggregation function: " + name); - } - } - + private static final PlotThdFactory thread_factory = new PlotThdFactory(); private static final class PlotThdFactory implements ThreadFactory { diff --git a/src/tsd/QueryRpc.java b/src/tsd/QueryRpc.java index 554e586b9a..1346352a0f 100644 --- a/src/tsd/QueryRpc.java +++ b/src/tsd/QueryRpc.java @@ -129,7 +129,7 @@ private void handleQuery(final TSDB tsdb, final HttpQuery query, expressions = null; } else { expressions = new ArrayList(); - data_query = this.parseQuery(tsdb, query, expressions); + data_query = parseQuery(tsdb, query); } if (query.getAPIMethod() == HttpMethod.DELETE && @@ -477,7 +477,7 @@ public String toString() { * @return A TSQuery if parsing was successful * @throws BadRequestException if parsing was unsuccessful */ - private TSQuery parseQuery(final TSDB tsdb, final HttpQuery query, + public static TSQuery parseQuery(final TSDB tsdb, final HttpQuery query) { final List expressions) { final TSQuery data_query = new TSQuery(); @@ -520,14 +520,14 @@ private TSQuery parseQuery(final TSDB tsdb, final HttpQuery query, if (query.hasQueryStringParam("tsuid")) { final List tsuids = query.getQueryStringParams("tsuid"); for (String q : tsuids) { - this.parseTsuidTypeSubQuery(q, data_query); + parseTsuidTypeSubQuery(q, data_query); } } if (query.hasQueryStringParam("m")) { final List legacy_queries = query.getQueryStringParams("m"); for (String q : legacy_queries) { - this.parseMTypeSubQuery(q, data_query); + parseMTypeSubQuery(q, data_query); } } @@ -570,7 +570,7 @@ private TSQuery parseQuery(final TSDB tsdb, final HttpQuery query, * @throws BadRequestException if we are unable to parse the query or it is * missing components */ - private void parseMTypeSubQuery(final String query_string, + private static void parseMTypeSubQuery(final String query_string, TSQuery data_query) { if (query_string == null || query_string.isEmpty()) { throw new BadRequestException("The query string was empty"); @@ -623,7 +623,7 @@ private void parseMTypeSubQuery(final String query_string, * @throws BadRequestException if we are unable to parse the query or it is * missing components */ - private void parseTsuidTypeSubQuery(final String query_string, + private static void parseTsuidTypeSubQuery(final String query_string, TSQuery data_query) { if (query_string == null || query_string.isEmpty()) { throw new BadRequestException("The tsuid query string was empty"); diff --git a/src/tsd/client/MetricForm.java b/src/tsd/client/MetricForm.java index 9a06606412..fd51faeef2 100644 --- a/src/tsd/client/MetricForm.java +++ b/src/tsd/client/MetricForm.java @@ -119,17 +119,82 @@ private String parseWithMetric(final String metric) { clearTags(); return metric.substring(0, len - 2); } + final int num_tags_before = getNumTags(); + + final List filters = new ArrayList(); + final int close = metric.indexOf('}'); + int i = 0; + if (close != metric.length() - 1) { // "foo{...}{tagk=filter}" + final int filter_bracket = metric.lastIndexOf('{'); + for (final String filter : metric.substring(filter_bracket + 1, + metric.length() - 1).split(",")) { + if (filter.isEmpty()) { + break; + } + final String[] kv = filter.split("="); + if (kv.length != 2 || kv[0].isEmpty() || kv[1].isEmpty()) { + continue; // Invalid tag. + } + final Filter f = new Filter(); + f.tagk = kv[0]; + f.tagv = kv[1]; + f.is_groupby = false; + filters.add(f); + i++; + } + } + + i = 0; + for (final String tag : metric.substring(curly + 1, close).split(",")) { + if (tag.isEmpty() && close != metric.length() - 1){ + break; + } + final String[] kv = tag.split("="); + if (kv.length != 2 || kv[0].isEmpty() || kv[1].isEmpty()) { + continue; // Invalid tag. + } + final Filter f = new Filter(); + f.tagk = kv[0]; + f.tagv = kv[1]; + f.is_groupby = true; + filters.add(f); + i++; + } + + i = 0; + for (int x = filters.size() - 1; x >= 0; x--) { + final Filter filter = filters.get(x); + if (i < num_tags_before) { + setTag(i++, filter.tagk, filter.tagv, filter.is_groupby); + } else { + addTag(filter.tagk, filter.tagv, filter.is_groupby); + } + } + + if (i < num_tags_before) { + setTag(i, "", "", true); + } else { + addTag(); + } + // Remove extra tags. + for (i++; i < num_tags_before; i++) { + tagtable.removeRow(i + 1); + } + // Return the "foo" part of "foo{a=b,...,x=y}" + return metric.substring(0, curly); + + /* // substring the tags out of "foo{a=b,...,x=y}" and parse them. int i = 0; // Tag index. final int num_tags_before = getNumTags(); for (final String tag : metric.substring(curly + 1, len - 1).split(",")) { final String[] kv = tag.split("="); if (kv.length != 2 || kv[0].isEmpty() || kv[1].isEmpty()) { - setTag(i, "", ""); + setTag(i, "", "", true); continue; // Invalid tag. } if (i < num_tags_before) { - setTag(i, kv[0], kv[1]); + setTag(i, kv[0], kv[1], true); } else { addTag(kv[0], kv[1]); } @@ -137,7 +202,7 @@ private String parseWithMetric(final String metric) { } // Leave an empty line at the end. if (i < num_tags_before) { - setTag(i, "", ""); + setTag(i, "", "", true); } else { addTag(); } @@ -146,7 +211,7 @@ private String parseWithMetric(final String metric) { tagtable.removeRow(i + 1); } // Return the "foo" part of "foo{a=b,...,x=y}" - return metric.substring(0, curly); + return metric.substring(0, curly); */ } public void updateFromQueryString(final String m, final String o) { @@ -353,13 +418,42 @@ public boolean buildQueryString(final StringBuilder url) { } } url.append(':').append(metric); + boolean non_groupbys = false; + int groupby_tags = 0; { final int ntags = getNumTags(); url.append('{'); for (int tag = 0; tag < ntags; tag++) { final String tagname = getTagName(tag); final String tagvalue = getTagValue(tag); - if (tagname.isEmpty() || tagvalue.isEmpty()) { + if (tagname.isEmpty() || tagvalue.isEmpty() || !isTagGroupby(tag)) { + if (!isTagGroupby(tag)) { + non_groupbys = true; + } + continue; + } + url.append(tagname).append('=').append(tagvalue) + .append(','); + ++groupby_tags; + } + final int last = url.length() - 1; + if (url.charAt(last) == '{') { // There was no tag. + url.setLength(last); // So remove the `{'. + } else { // Need to replace the last `,' with a `}'. + url.setCharAt(url.length() - 1, '}'); + } + } + if (non_groupbys) { + if (groupby_tags == 0) { + // need this to shift group by to non-group by + url.append("{}"); + } + final int ntags = getNumTags(); + url.append('{'); + for (int tag = 0; tag < ntags; tag++) { + final String tagname = getTagName(tag); + final String tagvalue = getTagValue(tag); + if (tagname.isEmpty() || tagvalue.isEmpty() || isTagGroupby(tag)) { continue; } url.append(tagname).append('=').append(tagvalue) @@ -390,6 +484,10 @@ private String getTagName(final int i) { private String getTagValue(final int i) { return ((SuggestBox) tagtable.getWidget(i + 1, 2)).getValue(); } + + private boolean isTagGroupby(final int i) { + return ((CheckBox) tagtable.getWidget(i + 1, 3)).getValue(); + } private void setTagName(final int i, final String value) { ((SuggestBox) tagtable.getWidget(i + 1, 1)).setValue(value); @@ -399,6 +497,10 @@ private void setTagValue(final int i, final String value) { ((SuggestBox) tagtable.getWidget(i + 1, 2)).setValue(value); } + private void isTagGroupby(final int i, final boolean groupby) { + ((CheckBox) tagtable.getWidget(i + 1, 3)).setValue(groupby); + } + /** * Changes the name/value of an existing tag. * @param i The index of the tag to change. @@ -406,27 +508,34 @@ private void setTagValue(final int i, final String value) { * @param value The new value of the tag. * Requires: {@code i < getNumTags()}. */ - private void setTag(final int i, final String name, final String value) { + private void setTag(final int i, final String name, final String value, + final boolean groupby) { setTagName(i, name); setTagValue(i, value); + isTagGroupby(i, groupby); } private void addTag() { - addTag(null, null); + addTag(null, null, true); } private void addTag(final String default_tagname) { - addTag(default_tagname, null); + addTag(default_tagname, null, true); } private void addTag(final String default_tagname, - final String default_value) { + final String default_value, + final boolean is_groupby) { final int row = tagtable.getRowCount(); final ValidatedTextBox tagname = new ValidatedTextBox(); final SuggestBox suggesttagk = RemoteOracle.newSuggestBox("tagk", tagname); final ValidatedTextBox tagvalue = new ValidatedTextBox(); final SuggestBox suggesttagv = RemoteOracle.newSuggestBox("tagv", tagvalue); + final CheckBox groupby = new CheckBox(); + groupby.setValue(is_groupby); + groupby.setTitle("Group by"); + groupby.addClickHandler(events_handler); tagname.setValidationRegexp(TSDB_ID_RE); tagvalue.setValidationRegexp(TSDB_TAGVALUE_RE); tagname.setWidth("100%"); @@ -440,6 +549,7 @@ private void addTag(final String default_tagname, tagtable.setWidget(row, 1, suggesttagk); tagtable.setWidget(row, 2, suggesttagv); + tagtable.setWidget(row, 3, groupby); if (row > 2) { final Button remove = new Button("x"); remove.addClickHandler(removetag); @@ -457,7 +567,7 @@ private void addTag(final String default_tagname, } private void clearTags() { - setTag(0, "", ""); + setTag(0, "", "", true); for (int i = getNumTags() - 1; i > 1; i++) { tagtable.removeRow(i + 1); } @@ -493,9 +603,10 @@ public void onBlur(final BlurEvent event) { for (int tag = 1; tag < ntags; tag++) { final String tagname = getTagName(tag); final String tagvalue = getTagValue(tag); - setTag(tag - 1, tagname, tagvalue); + // todo - groupby + setTag(tag - 1, tagname, tagvalue, isTagGroupby(tag)); } - setTag(ntags - 1, "", ""); + setTag(ntags - 1, "", "", true); } // Try to remove empty lines from the tag table (but never remove the // first line or last line, even if they're empty). Walk the table @@ -620,6 +731,12 @@ static final public LocalRateOptions parseRateOptions(boolean rate, String spec) } } + private static class Filter { + String tagk; + String tagv; + boolean is_groupby; + } + // ------------------- // // Focusable interface // // ------------------- // From 6355773624a289202ac139eb83d794adaf6fe289 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Tue, 15 Dec 2015 16:36:58 -0800 Subject: [PATCH 099/233] Add miss merge fixes... doh Signed-off-by: Chris Larsen --- src/tsd/GraphHandler.java | 2 +- src/tsd/QueryRpc.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tsd/GraphHandler.java b/src/tsd/GraphHandler.java index 6f02526a3b..d6ce433e13 100644 --- a/src/tsd/GraphHandler.java +++ b/src/tsd/GraphHandler.java @@ -851,7 +851,7 @@ private static void printMetricHeader(final PrintWriter writer, final String met * @throws IllegalArgumentException if the metric or tags were malformed. */ private static Query[] parseQuery(final TSDB tsdb, final HttpQuery query) { - final TSQuery q = QueryRpc.parseQuery(tsdb, query); + final TSQuery q = QueryRpc.parseQuery(tsdb, query, null); q.validateAndSetQuery(); return q.buildQueries(tsdb); } diff --git a/src/tsd/QueryRpc.java b/src/tsd/QueryRpc.java index 1346352a0f..2c93eeef1e 100644 --- a/src/tsd/QueryRpc.java +++ b/src/tsd/QueryRpc.java @@ -129,7 +129,7 @@ private void handleQuery(final TSDB tsdb, final HttpQuery query, expressions = null; } else { expressions = new ArrayList(); - data_query = parseQuery(tsdb, query); + data_query = parseQuery(tsdb, query, expressions); } if (query.getAPIMethod() == HttpMethod.DELETE && @@ -477,7 +477,7 @@ public String toString() { * @return A TSQuery if parsing was successful * @throws BadRequestException if parsing was unsuccessful */ - public static TSQuery parseQuery(final TSDB tsdb, final HttpQuery query) { + public static TSQuery parseQuery(final TSDB tsdb, final HttpQuery query, final List expressions) { final TSQuery data_query = new TSQuery(); From 6ffe3cbae1eb67ce3a500c68c4923723c5232694 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Tue, 15 Dec 2015 16:49:35 -0800 Subject: [PATCH 100/233] Close #658, an ugly oversight around case insensitivity in the iwildcard filter. Signed-off-by: Chris Larsen --- src/query/filter/TagVWildcardFilter.java | 2 +- test/query/filter/TestTagVLiteralOrFilter.java | 9 +++++++++ test/query/filter/TestTagVWildcardFilter.java | 8 ++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/query/filter/TagVWildcardFilter.java b/src/query/filter/TagVWildcardFilter.java index 2fae7a099f..82d8da97ab 100644 --- a/src/query/filter/TagVWildcardFilter.java +++ b/src/query/filter/TagVWildcardFilter.java @@ -119,7 +119,7 @@ public Deferred match(final Map tags) { // match all return Deferred.fromResult(true); } else if (case_insensitive) { - tags.get(tagk).toLowerCase(); + tagv = tags.get(tagk).toLowerCase(); } if (has_postfix && !has_prefix && !tagv.endsWith(components[components.length-1])) { diff --git a/test/query/filter/TestTagVLiteralOrFilter.java b/test/query/filter/TestTagVLiteralOrFilter.java index 2208657bc7..fbc14bfc23 100644 --- a/test/query/filter/TestTagVLiteralOrFilter.java +++ b/test/query/filter/TestTagVLiteralOrFilter.java @@ -85,6 +85,15 @@ public void matchCaseInsensitive() throws Exception { assertTrue(((TagVLiteralOrFilter)filter).isCaseInsensitive()); } + @Test + public void matchCaseInsensitiveValue() throws Exception { + tags.put(TAGK, "CMTDIBBLER"); + TagVFilter filter = new TagVLiteralOrFilter(TAGK, + "LutZe|CMtDibbler|Slant", true); + assertTrue(filter.match(tags).join()); + assertTrue(((TagVLiteralOrFilter)filter).isCaseInsensitive()); + } + @Test public void matchCaseInsensitiveFail() throws Exception { TagVFilter filter = new TagVLiteralOrFilter(TAGK, diff --git a/test/query/filter/TestTagVWildcardFilter.java b/test/query/filter/TestTagVWildcardFilter.java index 3270d49149..2c1daf1c52 100644 --- a/test/query/filter/TestTagVWildcardFilter.java +++ b/test/query/filter/TestTagVWildcardFilter.java @@ -259,6 +259,14 @@ public void matchPostfixCaseInsensitive() throws Exception { assertTrue(filter.match(tags).join()); } + @Test + public void matchPostfixCaseInsensitiveValue() throws Exception { + tags.put(TAGK, "ogg-01.ops.ankh.MORPORK.com"); + TagVFilter filter = new TagVWildcardFilter(TAGK, + "*.MorPork.com", true); + assertTrue(filter.match(tags).join()); + } + @Test public void matchPrefixCaseInsensitive() throws Exception { TagVFilter filter = new TagVWildcardFilter(TAGK, From d888f00fd78a8a619579471a2cbc56ea9822fff5 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Wed, 16 Dec 2015 13:26:04 -0800 Subject: [PATCH 101/233] Link to the new GWT Theme jar. Add the header image for the built in UI. Update the favicon. Signed-off-by: Chris Larsen --- Makefile.am | 12 ++++++++---- pom.xml.in | 6 ++++++ src/tsd/HttpQuery.java | 11 ++++------- src/tsd/QueryUi.gwt.xml | 2 +- src/tsd/RpcManager.java | 2 +- src/tsd/static/favicon.ico | Bin 1150 -> 1150 bytes src/tsd/static/opentsdb_header.jpg | Bin 0 -> 7519 bytes third_party/gwt/include.mk | 9 ++++++++- .../gwt/opentsdb-gwt-theme-1.0.0.jar.md5 | 1 + 9 files changed, 29 insertions(+), 14 deletions(-) mode change 100644 => 100755 src/tsd/static/favicon.ico create mode 100755 src/tsd/static/opentsdb_header.jpg create mode 100644 third_party/gwt/opentsdb-gwt-theme-1.0.0.jar.md5 diff --git a/Makefile.am b/Makefile.am index 7ce950db64..35d7b5b055 100644 --- a/Makefile.am +++ b/Makefile.am @@ -404,7 +404,9 @@ expr_src_dir = $(builddir)/src/$(expr_package) get_expr_classes = `classes=''; for f in $(packagedir)$(expr_package)/*.class; do classes="$$classes $$f"; done; echo $$classes;` #dist_pkgdata_DATA = src/logback.xml -dist_static_DATA = src/tsd/static/favicon.ico +dist_static_DATA = \ + src/tsd/static/favicon.ico \ + src/tsd/static/openTSDB_header.jpg EXTRA_DIST = tsdb.in $(tsdb_SRC) $(test_SRC) $(expr_grammar) \ $(test_plugin_SRC) $(test_plugin_MF) $(test_plugin_SVCS:%=test/%) \ @@ -502,14 +504,14 @@ get_dep_classpath = `for jar in $(tsdb_DEPS); do $(find_jar); done | tr '\n' ':' @touch "$@" VALIDATION_API_CLASSPATH = `jar=$(VALIDATION_API); $(find_jar)`:`jar=$(VALIDATION_API_SOURCES); $(find_jar)` -GWT_CLASSPATH = $(VALIDATION_API_CLASSPATH):`jar=$(GWT_DEV); $(find_jar)`:`jar=$(GWT_USER); $(find_jar)`:$(srcdir)/src +GWT_CLASSPATH = $(VALIDATION_API_CLASSPATH):`jar=$(GWT_DEV); $(find_jar)`:`jar=$(GWT_USER); $(find_jar)`:`jar=$(GWT_THEME); $(find_jar)`:$(srcdir)/src # The GWT compiler is way too slow, that's not very Googley. So we save the # MD5 of the files we compile in the stamp file and everytime `make' things it # needs to recompile the GWT code, we verify whether the code really changed # or whether it's just a file that was touched (which happens frequently when # using Git while rebasing and whatnot). gwtc: .gwtc-stamp -.gwtc-stamp: $(httpui_SRC) $(httpui_DEPS) $(VALIDATION_API) $(VALIDATION_API_SOURCES) $(GWT_DEV) $(GWT_USER) +.gwtc-stamp: $(httpui_SRC) $(httpui_DEPS) $(VALIDATION_API) $(VALIDATION_API_SOURCES) $(GWT_DEV) $(GWT_USER) $(GWT_THEME) @$(mkdir_p) gwt { cd $(srcdir) && cat $(httpui_SRC); } | $(MD5) >"$@-t" cmp -s "$@" "$@-t" && exit 0; \ @@ -773,6 +775,7 @@ pom.xml: pom.xml.in Makefile -e 's/@ASYNCCASSANDRA_VERSION@/$(ASYNCCASSANDRA_VERSION)/' \ -e 's/@GUAVA_VERSION@/$(GUAVA_VERSION)/' \ -e 's/@GWT_VERSION@/$(GWT_VERSION)/' \ + -e 's/@GWT_THEME_VERSION@/$(GWT_THEME_VERSION)/' \ -e 's/@HAMCREST_VERSION@/$(HAMCREST_VERSION)/' \ -e 's/@JACKSON_VERSION@/$(JACKSON_VERSION)/' \ -e 's/@JAVASSIST_VERSION@/$(JAVASSIST_VERSION)/' \ @@ -845,7 +848,8 @@ debian: dist staticroot chmod 755 $(distdir)/debian/DEBIAN/* cp $(top_srcdir)/build-aux/deb/init.d/opentsdb $(distdir)/debian/etc/init.d cp $(jar) $(distdir)/debian/usr/share/opentsdb/lib - cp -r staticroot/favicon.ico $(distdir)/debian/usr/share/opentsdb/static + cp -r staticroot/icon.ico $(distdir)/debian/usr/share/opentsdb/static + cp -r staticroot/openTSDB_header.jpg $(distdir)/debian/usr/share/opentsdb/static cp -r gwt/queryui/* $(distdir)/debian/usr/share/opentsdb/static `for dep_jar in $(tsdb_DEPS); do cp $$dep_jar \ $(distdir)/debian/usr/share/opentsdb/lib; done;` diff --git a/pom.xml.in b/pom.xml.in index e5914cd031..c44b692ff9 100644 --- a/pom.xml.in +++ b/pom.xml.in @@ -479,6 +479,12 @@ gwt-user @GWT_VERSION@ + + + net.opentsdb + opentsdb_gwt_theme + @GWT_THEME_VERSION@ + diff --git a/src/tsd/HttpQuery.java b/src/tsd/HttpQuery.java index bea01cb6f2..c2848ee7aa 100644 --- a/src/tsd/HttpQuery.java +++ b/src/tsd/HttpQuery.java @@ -1017,7 +1017,6 @@ protected Logger logger() { + "body{font-family:arial,sans-serif;margin-left:2em}" + "A.l:link{color:#6f6f6f}" + "A.u:link{color:green}" - + ".subg{background-color:#e2f4f7}" + ".fwf{font-family:monospace;white-space:pre-wrap}" + "//-->"; @@ -1025,12 +1024,10 @@ protected Logger logger() { "\n" + "" + "" - + "" - + "" + + "" diff --git a/src/tsd/QueryUi.gwt.xml b/src/tsd/QueryUi.gwt.xml index 0bf3faf297..781cb128d5 100644 --- a/src/tsd/QueryUi.gwt.xml +++ b/src/tsd/QueryUi.gwt.xml @@ -1,7 +1,7 @@ - + diff --git a/src/tsd/RpcManager.java b/src/tsd/RpcManager.java index 99ecd57eb0..1814326c34 100644 --- a/src/tsd/RpcManager.java +++ b/src/tsd/RpcManager.java @@ -568,7 +568,7 @@ public void execute(final TSDB tsdb, final HttpQuery query) query.sendReply(HttpQuery.makePage( "", - "TSD", "Time Series Database", buf.toString())); + "OpenTSDB", "", buf.toString())); } } diff --git a/src/tsd/static/favicon.ico b/src/tsd/static/favicon.ico old mode 100644 new mode 100755 index 954d3c335e1a84af8551518b391caee26dab0913..b2d9ef22452baeca1a5bdd1676e7865921cb8713 GIT binary patch literal 1150 zcmbu8OHUI~6vqcO7)`V_zPhPa5*{v0V~jB|(rH5=7%ZfjU_XExh#1q9c4jKYG^QOd zvNbs%I{fbK~$4|bh`{RveL%A5N`&HNBLN*|R;5=B;?mOVS zWvEw!>&oTeYQpW#kBM&b)$r=cmU9Mth$8}`HMs~3-`*SJiLTx?6sKw^my8Yhf>9i8 z3wQ0Xu+N${{>0=`^Wz;}=%F5oMgQmQPr!ZF6d(K;>)()_=DOnI8;XNtt!!{Bt%=qI zrx?`OhSm`EgIwYld`IN>?dAC3N=({}omfK+j=p(mq$%A~(KM=Ejj^V-3ru`y4mWV< zq&1EnZDk_8<#ejM@;cYCJN|WUvhyYKQl%H3RPOELEY|$qvz19x4C+{krKdB+WOEAI zh)8ktW(*_Klg%ty7@VOP#4^;S+;7wG!mKpn0ZnXfqAT6GleMtcOm8{E(3?scNXzE1 zz?ZZKeaJCuS~;{c(m}nL%6W>Z3@t~4)GNh+BiaMn3yMK+VZ1Z_Uudr;I5a?4i$;5i zZ_3N^>D_0`(Gcwi#Q@5k1v>Ke{FwhXd@^!%jC%Jc7A_`RL&(?hO>|Rsr~@l;FV@#7 z2E0dZ2as^CLVB{*v49!{)-FK{Xuq+VxON?9iO!{j8h1Wq?m65U_Xqj*`%Ashe*C@9QOuv)Y`VXq&E6LT&X2=y`IRd@ literal 1150 zcmb`DT}V@59L8VC=q{v-E=%YlKe8mP3ojJIw3}|q{78bL6zxk&9at8XnQ6lik|0FU zY_>A3nMuv9rpwxrbodboxm9NS@FUOK+~(%f`@Z8G^CAf99G?B3J8y~>ngbBkRhp^!D&+0ZIRoPU05D~1Q{VuriM2S~k@jywHL zaQWO)-eyuEtubBtP7UYPr?)|?3BYDsu1w!Yk*&!~vOm_{Xx^ft&-b)MRm;KChUVA1MUnlHXw>E&L#sHk! zdRQ_EEZM7J$qI$rq>(t$Y>D(r{HwFuc`UE?V^g>n+Uz};e^Cbn{FrVjfw^D<1pR#x z#l^Z$rH!)6WO&_Mw?8FO%ssU?;@<8?_qy!KC+sNN;zwKrYJIv_w^?Z&xMC$chr zKvwL8-5!v37|YZzi^t3dJ!ao3R&;8$2N%u@sH${gXwbK!6IqdWPmcVt|9$uWdqOUS V{H0bAB40yD%|=2RLFjKMR&<%6~d0H9D201e<@ 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 Date: Wed, 16 Dec 2015 18:11:07 -0800 Subject: [PATCH 102/233] Fix case on the new UI header filename. Signed-off-by: Chris Larsen --- Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index 35d7b5b055..ad2457734c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -406,7 +406,7 @@ get_expr_classes = `classes=''; for f in $(packagedir)$(expr_package)/*.class; d #dist_pkgdata_DATA = src/logback.xml dist_static_DATA = \ src/tsd/static/favicon.ico \ - src/tsd/static/openTSDB_header.jpg + src/tsd/static/opentsdb_header.jpg EXTRA_DIST = tsdb.in $(tsdb_SRC) $(test_SRC) $(expr_grammar) \ $(test_plugin_SRC) $(test_plugin_MF) $(test_plugin_SVCS:%=test/%) \ From 7438eefe12d16b9485bed6cede33954d2aa4e05e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A7=AC=E5=B9=B3?= Date: Wed, 9 Dec 2015 17:41:19 +0800 Subject: [PATCH 103/233] build shell scan all salted metrics Signed-off-by: Chris Larsen --- src/tools/DumpSeries.java | 81 ++++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/src/tools/DumpSeries.java b/src/tools/DumpSeries.java index a090f1e851..a7e9222fcc 100644 --- a/src/tools/DumpSeries.java +++ b/src/tools/DumpSeries.java @@ -16,6 +16,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Date; +import java.util.List; import java.util.Map; import org.hbase.async.DeleteRequest; @@ -96,49 +97,51 @@ private static void doDump(final TSDB tsdb, final StringBuilder buf = new StringBuilder(); for (final Query query : queries) { - final Scanner scanner = Internal.getScanner(query); - ArrayList> rows; - while ((rows = scanner.nextRows().joinUninterruptibly()) != null) { - for (final ArrayList row : rows) { - buf.setLength(0); - final byte[] key = row.get(0).key(); - final long base_time = Internal.baseTime(tsdb, key); - final String metric = Internal.metricName(tsdb, key); - // Print the row key. - if (!importformat) { - buf.append(Arrays.toString(key)) - .append(' ') - .append(metric) - .append(' ') - .append(base_time) - .append(" (").append(date(base_time)).append(") "); - try { - buf.append(Internal.getTags(tsdb, key)); - } catch (RuntimeException e) { - buf.append(e.getClass().getName() + ": " + e.getMessage()); - } - buf.append('\n'); - System.out.print(buf); - } - - // Print individual cells. - buf.setLength(0); - if (!importformat) { - buf.append(" "); - } - for (final KeyValue kv : row) { - // Discard everything or keep initial spaces. - buf.setLength(importformat ? 0 : 2); - formatKeyValue(buf, tsdb, importformat, kv, base_time, metric); - if (buf.length() > 0) { + final List scanners = Internal.getScanners(query); + for (Scanner scanner : scanners) { + ArrayList> rows; + while ((rows = scanner.nextRows().joinUninterruptibly()) != null) { + for (final ArrayList row : rows) { + buf.setLength(0); + final byte[] key = row.get(0).key(); + final long base_time = Internal.baseTime(tsdb, key); + final String metric = Internal.metricName(tsdb, key); + // Print the row key. + if (!importformat) { + buf.append(Arrays.toString(key)) + .append(' ') + .append(metric) + .append(' ') + .append(base_time) + .append(" (").append(date(base_time)).append(") "); + try { + buf.append(Internal.getTags(tsdb, key)); + } catch (RuntimeException e) { + buf.append(e.getClass().getName() + ": " + e.getMessage()); + } buf.append('\n'); System.out.print(buf); } - } - if (delete) { - final DeleteRequest del = new DeleteRequest(table, key); - client.delete(del); + // Print individual cells. + buf.setLength(0); + if (!importformat) { + buf.append(" "); + } + for (final KeyValue kv : row) { + // Discard everything or keep initial spaces. + buf.setLength(importformat ? 0 : 2); + formatKeyValue(buf, tsdb, importformat, kv, base_time, metric); + if (buf.length() > 0) { + buf.append('\n'); + System.out.print(buf); + } + } + + if (delete) { + final DeleteRequest del = new DeleteRequest(table, key); + client.delete(del); + } } } } From 8add257fce0af41002bf77db80b4ec6af221f0fc Mon Sep 17 00:00:00 2001 From: Jonathan Creasy Date: Thu, 7 Jan 2016 17:42:13 -0800 Subject: [PATCH 104/233] Added script to install HBase, OpenTSDB and TCollector on OSX for development and demonstration --- tools/osx_full_stack_install.sh | 93 +++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 tools/osx_full_stack_install.sh diff --git a/tools/osx_full_stack_install.sh b/tools/osx_full_stack_install.sh new file mode 100644 index 0000000000..e409f03c7a --- /dev/null +++ b/tools/osx_full_stack_install.sh @@ -0,0 +1,93 @@ +#!/bin/bash +# +# Script which installs HBase, OpenTSDB and TCollector on OSX +# +# This file is part of OpenTSDB. +# Copyright (C) 2010-2012 The OpenTSDB Authors. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 2.1 of the License, or (at your +# option) any later version. This program is distributed in the hope that it +# will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +# General Public License for more details. You should have received a copy +# of the GNU Lesser General Public License along with this program. If not, +# see . +# +# +if [ $# -eq 0 ] + then + echo "No arguments supplied, please suggest an installation path, ex. $HOME" + exit 1; +fi +BASE_DIR=$1 +SUBBASE_DIR=opentsdb_stack; +if [ ! -d "${BASE_DIR}" ] ; then + echo "$BASE_DIR is not a directory"; + exit 1; +fi +export INSTALL_DIR=$BASE_DIR/$SUBBASE_DIR; +/bin/echo "Installing into $INSTALL_DIR"; +/bin/mkdir -p $INSTALL_DIR; +cd $INSTALL_DIR; +/usr/bin/curl -q http://mirror.cogentco.com/pub/apache/hbase/1.1.2/hbase-1.1.2-bin.tar.gz -o $INSTALL_DIR/hbase-1.1.2-bin.tar.gz 2>/dev/null; +/usr/bin/tar -xzvf hbase-1.1.2-bin.tar.gz -C $INSTALL_DIR/; +cd $INSTALL_DIR/hbase-1.1.2; +/bin/mkdir -p $INSTALL_DIR/data/hbase; +/bin/mkdir -p $INSTALL_DIR/data/zookeeper; +/bin/cat < conf/hbase-site.xml + + + + + + hbase.rootdir + file://$INSTALL_DIR/data/hbase + + + hbase.zookeeper.property.dataDir + $INSTALL_DIR/data/zookeeper + + +EOF +$INSTALL_DIR/hbase-1.1.2/bin/start-hbase.sh; +cd $INSTALL_DIR +/usr/bin/git clone https://github.com/OpenTSDB/opentsdb.git; +cd opentsdb +$INSTALL_DIR/opentsdb/build.sh clean; $INSTALL_DIR/opentsdb/build.sh; +/bin/mkdir $INSTALL_DIR/opentsdb/build/cache; +export HBASE_HOME=$INSTALL_DIR/hbase-1.1.2; +export COMPRESSION=NONE; +$INSTALL_DIR/opentsdb/src/create_table.sh; +$INSTALL_DIR/opentsdb/build/tsdb tsd --config=$INSTALL_DIR/opentsdb/src/opentsdb.conf --staticroot=$INSTALL_DIR/opentsdb/build/staticroot --cachedir=$INSTALL_DIR/opentsdb/build/cache --port=4242 --zkquorum=localhost:2181 --zkbasedir=/hbase --auto-metric & +cd $INSTALL_DIR +/usr/bin/git clone https://github.com/OpenTSDB/tcollector.git; +cd $INSTALL_DIR/tcollector/tcollector +/bin/rm -rf $INSTALL_DIR/tcollector/collectors/0/*; +/usr/bin/curl -q https://raw.githubusercontent.com/aalpern/tcollector-osx/master/dfstat.py -o $INSTALL_DIR/tcollector/collectors/0/dfstat.py 2>/dev/null; +/usr/bin/curl -q https://raw.githubusercontent.com/aalpern/tcollector-osx/master/iostat.py -o $INSTALL_DIR/tcollector/collectors/0/iostat.py 2>/dev/null; +/usr/bin/curl -q https://raw.githubusercontent.com/aalpern/tcollector-osx/master/vmstat.py -o $INSTALL_DIR/tcollector/collectors/0/vmstat.py 2>/dev/null; +/bin/chmod a+x collectors/0/*.py; +$INSTALL_DIR/tcollector/tcollector.py -L localhost:4242 -t host=`hostname` -t domain=dev -P $INSTALL_DIR/tcollector/tcollector.pid --logfile $INSTALL_DIR/tcollector/tcollector.log & +/bin/sleep 30; +/usr/bin/open http://localhost:4242/#start=10m-ago\&m=sum:df.inodes.free\&autoreload=15; From 95568bf0bd0fe74a6b5a97600cc946c7e757fd47 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Mon, 25 Jan 2016 19:40:21 -0800 Subject: [PATCH 105/233] Fix up the Makefile for debian creation with the updated UI files Signed-off-by: Chris Larsen --- Makefile.am | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile.am b/Makefile.am index ad2457734c..78abae4d5d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -848,8 +848,8 @@ debian: dist staticroot chmod 755 $(distdir)/debian/DEBIAN/* cp $(top_srcdir)/build-aux/deb/init.d/opentsdb $(distdir)/debian/etc/init.d cp $(jar) $(distdir)/debian/usr/share/opentsdb/lib - cp -r staticroot/icon.ico $(distdir)/debian/usr/share/opentsdb/static - cp -r staticroot/openTSDB_header.jpg $(distdir)/debian/usr/share/opentsdb/static + cp -r staticroot/favicon.ico $(distdir)/debian/usr/share/opentsdb/static + cp -r staticroot/opentsdb_header.jpg $(distdir)/debian/usr/share/opentsdb/static cp -r gwt/queryui/* $(distdir)/debian/usr/share/opentsdb/static `for dep_jar in $(tsdb_DEPS); do cp $$dep_jar \ $(distdir)/debian/usr/share/opentsdb/lib; done;` From 09ba49e1517d78fa1c10e96d01b55dc6820eedc7 Mon Sep 17 00:00:00 2001 From: Davide D'Amico Date: Mon, 4 Jan 2016 12:50:59 +0100 Subject: [PATCH 106/233] configure includes a sysconfdir option that is not present in Makefile.am. A sysconfigdir option is present, instead. This leads to tsdb using always a /etc/opentsdb config path that in FreeBSD is not the right one Signed-off-by: Chris Larsen --- Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index 78abae4d5d..15013eb61a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -455,7 +455,7 @@ printdeps: # This is kind of a hack, but I couldn't find a better way to adjust the paths # in the script before it gets installed... install-exec-hook: - script=tsdb; pkgdatadir='$(pkgdatadir)'; configdir='$(sysconfigdir)/etc/opentsdb'; \ + script=tsdb; pkgdatadir='$(pkgdatadir)'; configdir='$(prefix)/etc/opentsdb'; \ abs_srcdir=''; abs_builddir=''; $(edit_tsdb_script) cat tsdb.tmp >"$(DESTDIR)$(bindir)/tsdb" rm -f tsdb.tmp From 14157685c31abe112f14beaafbb322af9989d357 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A7=AC=E5=B9=B3?= Date: Sun, 17 Jan 2016 13:41:12 +0800 Subject: [PATCH 107/233] compare call in CompactionQueue.class Signed-off-by: Chris Larsen --- src/core/CompactionQueue.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/CompactionQueue.java b/src/core/CompactionQueue.java index 0f23b361d4..390167fb0c 100644 --- a/src/core/CompactionQueue.java +++ b/src/core/CompactionQueue.java @@ -792,16 +792,16 @@ public void run() { */ private static final class Cmp implements Comparator { - /** On how many bytes do we encode metrics IDs. */ - private final short metric_width; + /** The position with which the timestamp of metric starts. */ + private final short timestamp_pos; public Cmp(final TSDB tsdb) { - metric_width = tsdb.metrics.width(); + timestamp_pos = Const.SALT_WIDTH() + tsdb.metrics.width(); } @Override public int compare(final byte[] a, final byte[] b) { - final int c = Bytes.memcmp(a, b, metric_width, Const.TIMESTAMP_BYTES); + final int c = Bytes.memcmp(a, b, timestamp_pos, Const.TIMESTAMP_BYTES); // If the timestamps are equal, sort according to the entire row key. return c != 0 ? c : Bytes.memcmp(a, b); } From 42e6f9a82726eba54444aaea2874ad4300e7ff7c Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Mon, 25 Jan 2016 19:02:18 -0800 Subject: [PATCH 108/233] Cast to short Signed-off-by: Chris Larsen --- src/core/CompactionQueue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/CompactionQueue.java b/src/core/CompactionQueue.java index 390167fb0c..523f4a0d6e 100644 --- a/src/core/CompactionQueue.java +++ b/src/core/CompactionQueue.java @@ -796,7 +796,7 @@ private static final class Cmp implements Comparator { private final short timestamp_pos; public Cmp(final TSDB tsdb) { - timestamp_pos = Const.SALT_WIDTH() + tsdb.metrics.width(); + timestamp_pos = (short) (Const.SALT_WIDTH() + tsdb.metrics.width()); } @Override From df72269f41d6ce528d6778beadb9993fae4c9e87 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sat, 30 Jan 2016 17:21:55 -0800 Subject: [PATCH 109/233] Fix some error handling in the MetaSync utility where it could hang forever. This still needs some rework and UTs. Ugly. Signed-off-by: Chris Larsen --- src/tools/MetaSync.java | 251 +++++++++++++++++++++----------------- src/tools/UidManager.java | 3 +- 2 files changed, 138 insertions(+), 116 deletions(-) diff --git a/src/tools/MetaSync.java b/src/tools/MetaSync.java index b3c7510d23..5076ea2c4b 100644 --- a/src/tools/MetaSync.java +++ b/src/tools/MetaSync.java @@ -102,11 +102,28 @@ public MetaSync(final TSDB tsdb, final Scanner scanner, * Loops through the entire TSDB data set and exits when complete. */ public void run() { - // list of deferred calls used to act as a buffer final ArrayList> storage_calls = new ArrayList>(); final Deferred result = new Deferred(); + + final class ErrBack implements Callback { + @Override + public Object call(Exception e) throws Exception { + Throwable ex = e; + while (ex.getClass().equals(DeferredGroupException.class)) { + if (ex.getCause() == null) { + LOG.warn("Unable to get to the root cause of the DGE"); + break; + } + ex = ex.getCause(); + } + LOG.error("Sync thread failed with exception", ex); + result.callback(null); + return null; + } + } + final ErrBack err_back = new ErrBack(); /** * Called when we have encountered a previously un-processed UIDMeta object. @@ -349,7 +366,7 @@ final class MetaScanner implements Callback> rows) } for (final ArrayList row : rows) { - - final byte[] tsuid = UniqueId.getTSUIDFromKey(row.get(0).key(), - TSDB.metrics_width(), Const.TIMESTAMP_BYTES); - - // if the current tsuid is the same as the last, just continue - // so we save time - if (last_tsuid != null && Arrays.equals(last_tsuid, tsuid)) { - continue; - } - last_tsuid = tsuid; - - // see if we've already processed this tsuid and if so, continue - if (processed_tsuids.contains(Arrays.hashCode(tsuid))) { - continue; - } - tsuid_string = UniqueId.uidToString(tsuid); - - // add tsuid to the processed list - processed_tsuids.add(Arrays.hashCode(tsuid)); - - // we may have a new TSUID or UIDs, so fetch the timestamp of the - // row for use as the "created" time. Depending on speed we could - // parse datapoints, but for now the hourly row time is enough - final long timestamp = Bytes.getUnsignedInt(row.get(0).key(), - Const.SALT_WIDTH() + TSDB.metrics_width()); - - LOG.debug("[" + thread_id + "] Processing TSUID: " + tsuid_string + - " row timestamp: " + timestamp); - - // now process the UID metric meta data - final byte[] metric_uid_bytes = - Arrays.copyOfRange(tsuid, 0, TSDB.metrics_width()); - final String metric_uid = UniqueId.uidToString(metric_uid_bytes); - Long last_get = metric_uids.get(metric_uid); - - if (last_get == null || last_get == 0 || timestamp < last_get) { - // fetch and update. Returns default object if the meta doesn't - // exist, so we can just call sync on this to create a missing - // entry - final UidCB cb = new UidCB(UniqueIdType.METRIC, - metric_uid_bytes, timestamp); - final Deferred process_uid = UIDMeta.getUIDMeta(tsdb, - UniqueIdType.METRIC, metric_uid_bytes).addCallbackDeferring(cb); - storage_calls.add(process_uid); - metric_uids.put(metric_uid, timestamp); - } - - // loop through the tags and process their meta - final List tags = UniqueId.getTagsFromTSUID(tsuid_string); - int idx = 0; - for (byte[] tag : tags) { - final UniqueIdType type = (idx % 2 == 0) ? UniqueIdType.TAGK : - UniqueIdType.TAGV; - idx++; - final String uid = UniqueId.uidToString(tag); + try { + final byte[] tsuid = UniqueId.getTSUIDFromKey(row.get(0).key(), + TSDB.metrics_width(), Const.TIMESTAMP_BYTES); - // check the maps to see if we need to bother updating - if (type == UniqueIdType.TAGK) { - last_get = tagk_uids.get(uid); - } else { - last_get = tagv_uids.get(uid); + // if the current tsuid is the same as the last, just continue + // so we save time + if (last_tsuid != null && Arrays.equals(last_tsuid, tsuid)) { + continue; } - if (last_get != null && last_get != 0 && last_get <= timestamp) { + last_tsuid = tsuid; + + // see if we've already processed this tsuid and if so, continue + if (processed_tsuids.contains(Arrays.hashCode(tsuid))) { continue; } - - // fetch and update. Returns default object if the meta doesn't - // exist, so we can just call sync on this to create a missing - // entry - final UidCB cb = new UidCB(type, tag, timestamp); - final Deferred process_uid = UIDMeta.getUIDMeta(tsdb, type, tag) - .addCallbackDeferring(cb); - storage_calls.add(process_uid); - if (type == UniqueIdType.TAGK) { - tagk_uids.put(uid, timestamp); - } else { - tagv_uids.put(uid, timestamp); + tsuid_string = UniqueId.uidToString(tsuid); + + /** + * An error callback used to catch issues with a particular timeseries + * or UIDMeta such as a missing UID name. We want to continue + * processing when this happens so we'll just log the error and + * the user can issue a command later to clean up orphaned meta + * entries. + */ + final class RowErrBack implements Callback { + @Override + public Object call(Exception e) throws Exception { + Throwable ex = e; + while (ex.getClass().equals(DeferredGroupException.class)) { + if (ex.getCause() == null) { + LOG.warn("Unable to get to the root cause of the DGE"); + break; + } + ex = ex.getCause(); + } + if (ex.getClass().equals(IllegalStateException.class)) { + LOG.error("Invalid data when processing TSUID [" + + tsuid_string + "]: " + ex.getMessage()); + } else if (ex.getClass().equals(IllegalArgumentException.class)) { + LOG.error("Invalid data when processing TSUID [" + + tsuid_string + "]: " + ex.getMessage()); + } else if (ex.getClass().equals(NoSuchUniqueId.class)) { + LOG.warn("Timeseries [" + tsuid_string + + "] includes a non-existant UID: " + ex.getMessage()); + } else { + LOG.error("Unknown exception processing row: " + row, ex); + } + return null; + } } - } - - /** - * An error callback used to cache issues with a particular timeseries - * or UIDMeta such as a missing UID name. We want to continue - * processing when this happens so we'll just log the error and - * the user can issue a command later to clean up orphaned meta - * entries. - */ - final class ErrBack implements Callback, Exception> { - @Override - public Deferred call(Exception e) throws Exception { + // add tsuid to the processed list + processed_tsuids.add(Arrays.hashCode(tsuid)); + + // we may have a new TSUID or UIDs, so fetch the timestamp of the + // row for use as the "created" time. Depending on speed we could + // parse datapoints, but for now the hourly row time is enough + final long timestamp = Bytes.getUnsignedInt(row.get(0).key(), + Const.SALT_WIDTH() + TSDB.metrics_width()); + + LOG.debug("[" + thread_id + "] Processing TSUID: " + tsuid_string + + " row timestamp: " + timestamp); + + // now process the UID metric meta data + final byte[] metric_uid_bytes = + Arrays.copyOfRange(tsuid, 0, TSDB.metrics_width()); + final String metric_uid = UniqueId.uidToString(metric_uid_bytes); + Long last_get = metric_uids.get(metric_uid); + + if (last_get == null || last_get == 0 || timestamp < last_get) { + // fetch and update. Returns default object if the meta doesn't + // exist, so we can just call sync on this to create a missing + // entry + final UidCB cb = new UidCB(UniqueIdType.METRIC, + metric_uid_bytes, timestamp); + final Deferred process_uid = UIDMeta.getUIDMeta(tsdb, + UniqueIdType.METRIC, metric_uid_bytes) + .addCallbackDeferring(cb) + .addErrback(new RowErrBack()); + storage_calls.add(process_uid); + metric_uids.put(metric_uid, timestamp); + } + + // loop through the tags and process their meta + final List tags = UniqueId.getTagsFromTSUID(tsuid_string); + int idx = 0; + for (byte[] tag : tags) { + final UniqueIdType type = (idx % 2 == 0) ? UniqueIdType.TAGK : + UniqueIdType.TAGV; + idx++; + final String uid = UniqueId.uidToString(tag); - Throwable ex = e; - while (ex.getClass().equals(DeferredGroupException.class)) { - if (ex.getCause() == null) { - LOG.warn("Unable to get to the root cause of the DGE"); - break; - } - ex = ex.getCause(); + // check the maps to see if we need to bother updating + if (type == UniqueIdType.TAGK) { + last_get = tagk_uids.get(uid); + } else { + last_get = tagv_uids.get(uid); } - if (ex.getClass().equals(IllegalStateException.class)) { - LOG.error("Invalid data when processing TSUID [" + - tsuid_string + "]", ex); - } else if (ex.getClass().equals(IllegalArgumentException.class)) { - LOG.error("Invalid data when processing TSUID [" + - tsuid_string + "]", ex); - } else if (ex.getClass().equals(NoSuchUniqueId.class)) { - LOG.warn("Timeseries [" + tsuid_string + - "] includes a non-existant UID: " + ex.getMessage()); + if (last_get != null && last_get != 0 && last_get <= timestamp) { + continue; + } + + // fetch and update. Returns default object if the meta doesn't + // exist, so we can just call sync on this to create a missing + // entry + final UidCB cb = new UidCB(type, tag, timestamp); + final Deferred process_uid = + UIDMeta.getUIDMeta(tsdb, type, tag) + .addCallbackDeferring(cb) + .addErrback(new RowErrBack()); + storage_calls.add(process_uid); + if (type == UniqueIdType.TAGK) { + tagk_uids.put(uid, timestamp); } else { - LOG.error("Unmatched Exception: " + ex.getClass()); - throw e; + tagv_uids.put(uid, timestamp); } - - return Deferred.fromResult(false); } + // handle the timeseries meta last so we don't record it if one + // or more of the UIDs had an issue + final Deferred process_tsmeta = + TSMeta.getTSMeta(tsdb, tsuid_string) + .addCallbackDeferring(new TSMetaCB(tsuid, timestamp)) + .addErrback(new RowErrBack()); + storage_calls.add(process_tsmeta); + } catch (RuntimeException e) { + LOG.error("Processing row " + row + " failed with exception: " + + e.getMessage()); + LOG.debug("Row: " + row + " stack trace: ", e); } - - // handle the timeseries meta last so we don't record it if one - // or more of the UIDs had an issue - final Deferred process_tsmeta = - TSMeta.getTSMeta(tsdb, tsuid_string) - .addCallbackDeferring(new TSMetaCB(tsuid, timestamp)); - process_tsmeta.addErrback(new ErrBack()); - storage_calls.add(process_tsmeta); } /** diff --git a/src/tools/UidManager.java b/src/tools/UidManager.java index 5dccc5837e..5e4185d4f4 100644 --- a/src/tools/UidManager.java +++ b/src/tools/UidManager.java @@ -127,6 +127,7 @@ public static void main(String[] args) throws Exception { rc = runCommand(tsdb, table, idwidth, ignorecase, args); } finally { try { + LOG.info("Shutting down TSD...."); tsdb.getClient().shutdown().joinUninterruptibly(); LOG.info("Gracefully shutdown the TSD"); } catch (Exception e) { @@ -1026,7 +1027,7 @@ private static int metaSync(final TSDB tsdb) throws Exception { thread.join(); LOG.info("Thread [" + thread + "] Finished"); } - + LOG.info("All metasync threads have completed"); // make sure buffered data is flushed to storage before exiting tsdb.flush().joinUninterruptibly(); From e0996c4215e77877d7f4808f20fe05b0d1e02b4b Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sat, 30 Jan 2016 18:02:11 -0800 Subject: [PATCH 110/233] Fix #686 where the first batch of scanned data was returned but the rest wasn't if the limit was greater than 128. Thanks @mgoralczyk-viasat Signed-off-by: Chris Larsen --- src/search/TimeSeriesLookup.java | 12 ++++++------ test/search/TestTimeSeriesLookup.java | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/search/TimeSeriesLookup.java b/src/search/TimeSeriesLookup.java index ff66760b0f..38b66efb9f 100644 --- a/src/search/TimeSeriesLookup.java +++ b/src/search/TimeSeriesLookup.java @@ -187,7 +187,8 @@ public Deferred> lookupAsync() { limit = 0; } - class ScannerCB implements Callback, ArrayList>> { + class ScannerCB implements Callback>, + ArrayList>> { private final Scanner scanner; // used to avoid dupes when scanning the data table private byte[] last_tsuid = null; @@ -198,11 +199,11 @@ class ScannerCB implements Callback, ArrayList> } Deferred> scan() { - return scanner.nextRows().addCallback(this); + return scanner.nextRows().addCallbackDeferring(this); } @Override - public List call(final ArrayList> rows) + public Deferred> call(final ArrayList> rows) throws Exception { if (rows == null) { scanner.close(); @@ -210,7 +211,7 @@ public List call(final ArrayList> rows) LOG.debug("Lookup query matched " + tsuids.size() + " time series in " + (System.currentTimeMillis() - start) + " ms"); } - return tsuids; + return Deferred.fromResult(tsuids); } for (final ArrayList row : rows) { @@ -260,8 +261,7 @@ public List call(final ArrayList> rows) ++rows_read; } - scan(); - return tsuids; + return scan(); } @Override diff --git a/test/search/TestTimeSeriesLookup.java b/test/search/TestTimeSeriesLookup.java index d07c14501b..60ededd380 100644 --- a/test/search/TestTimeSeriesLookup.java +++ b/test/search/TestTimeSeriesLookup.java @@ -519,6 +519,25 @@ public void limitVerification() throws Exception { assertArrayEquals(test_tsuids.get(0), tsuids.get(0)); } + @Test (expected = RuntimeException.class) + public void scannerException() throws Exception { + generateData(tsdb, storage); + final byte[] row = Const.SALT_WIDTH() > 0 ? + MockBase.stringToBytes( + "0300000400000000000001000001000003000005") : + MockBase.stringToBytes( + "00000400000000000001000001000003000005"); + storage.throwException(row, new RuntimeException("Boo!")); + final List> tags = + new ArrayList>(1); + tags.add(new Pair(TAGK_STRING, TAGV_STRING)); + final SearchQuery query = new SearchQuery(tags); + query.setUseMeta(false); + query.setLimit(1); + final TimeSeriesLookup lookup = new TimeSeriesLookup(tsdb, query); + lookup.lookup(); + } + // TODO test the dump to stdout /** From f82ebe1eeafa37311af8033c11af6382e9234d44 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sat, 30 Jan 2016 18:31:46 -0800 Subject: [PATCH 111/233] Fix #679 where an NPE is thrown if the logback config doesn't have a cyclic buffered appender to keep track of the last x log lines. Now it will throw a BadRequest exception with a useful message. Signed-off-by: Chris Larsen --- src/tsd/LogsRpc.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/tsd/LogsRpc.java b/src/tsd/LogsRpc.java index fab9581415..7aa91259a7 100644 --- a/src/tsd/LogsRpc.java +++ b/src/tsd/LogsRpc.java @@ -93,6 +93,11 @@ public LogIterator() { final Logger root = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); logbuf = (CyclicBufferAppender) root.getAppender("CYCLIC"); + if (logbuf == null) { + throw new BadRequestException( + "No CyclicBufferAppender found. Please configure logback " + + "to store the latest log entries."); + } } public Iterator iterator() { From 3dda9dcbf1b625e5fbecc0667f50d4f4ab975309 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Mon, 1 Feb 2016 10:42:06 -0800 Subject: [PATCH 112/233] Bump the ZK version to fix #671. Signed-off-by: Chris Larsen --- third_party/zookeeper/include.mk | 2 +- third_party/zookeeper/zookeeper-3.4.5.jar | Bin 0 -> 779974 bytes third_party/zookeeper/zookeeper-3.4.5.jar.md5 | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 third_party/zookeeper/zookeeper-3.4.5.jar create mode 100644 third_party/zookeeper/zookeeper-3.4.5.jar.md5 diff --git a/third_party/zookeeper/include.mk b/third_party/zookeeper/include.mk index 69368ea853..514b9dc5ed 100644 --- a/third_party/zookeeper/include.mk +++ b/third_party/zookeeper/include.mk @@ -13,7 +13,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see . -ZOOKEEPER_VERSION := 3.3.6 +ZOOKEEPER_VERSION := 3.4.5 ZOOKEEPER := third_party/zookeeper/zookeeper-$(ZOOKEEPER_VERSION).jar ZOOKEEPER_BASE_URL := http://central.maven.org/maven2/org/apache/zookeeper/zookeeper/$(ZOOKEEPER_VERSION) diff --git a/third_party/zookeeper/zookeeper-3.4.5.jar b/third_party/zookeeper/zookeeper-3.4.5.jar new file mode 100644 index 0000000000000000000000000000000000000000..a7966bbbce49344a67438bee8bb0cd1fd4952eee GIT binary patch literal 779974 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

N}hR{0dLySoh!!h~(WZKRed%U$2{V7gCf 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 z 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

r6T9k`3Qu3|?nk%Zsi+St;89-&5yV4X1>E zoDJQnyX@P`9Nut1-WX7A4*`AmaX!e)rTf?CzW}g)o?-QHiWO<>pzXaomcfFS@r&t{ubJP4f}1tTO>03`wv{?9;(f^B-g z9gN#)6Xq3_4-{H!@W0qfD~dFOCC8>_Ztl}-2M236{M~+_vxByvnv*A#w3wp>HU`Mj znzR!(SmpVZMWx1~BdTFCO$RaK@L^8eP`X)&ZH*+CFM_vP;RR4vSA-sgl?X4N+>8Dz z4_&AD0}Ux)3Ts9Ze}lRWM*s5a^K5x9pze|1z+059BH;3^?iWz3985K#4aQ$|UfhS5 z3qw5A-$VgecZ(A8i@buOCd3`)s}To!kx7E+zeHU&H}rQf{EAuRp){FkNl#Z4&4Gj| z5=bNm*;62{Y=y(f$K)W47}Y=Zo^-osWV45{P|FO)$Cu|AEe&cDwzfX9 zYQ!%XmX~F8Mk-@*$nEyg2gZLqrWE4;u7X~7JU4FFI*j(O`Ey8wJ0;2+OK8p~z9nT@ z&gvlZBJ%P_-e6MQvc=3P*tuCCwMswBXkHoD;~?j>jX$@c{%{Dh!5D!J6sx}FK$3a+ za%MtGz*H?75@3f9n{^)A~eYczgYw^+R>kWAm*;*b_h8)0I5-lQ5IU_M`6A-SErv z%xOrkB)KJJLdnk3yOTvi^YymJ)>K+Ptfo`RY<--M*Q9&B-sw9Hipvu_fP(vF&$*DEXtO_~EqqrtE zDVIk(1_V~6y9TDys)-scoQYYiU3=bxzuOi4>TtPl+NZ=A85m z&ATFuzj4uIGTsTM9H?qhrH^q+jbg*9y*EX|JFG^^mUTh8N+qMlY|W~rm$ofnQ7mx#u*7=N>_xraf1V4LpoqXKsyC0$_r!qcyXrB= zy6S<}wY!EMuoSA-u4>z|q^7M)YK*aQj-vUcq^+A;K(DTDQckm^8gytSt!rFmZFchRgJ%XN#3yh+`Pq0&CB1eN?+nFyTtFTM1Jw1N$ct*3eE#^%XeyUVVMeNbU1+0%aQ9I?eq zyYgoBTvxlxyy}JSDKO2u0ug*qyyd+pnPB5wKJ~pZ%~=G4c}d>}yugq6{fC_aQ6UeC z%0D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 z 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≥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%0D>1+d;giZJ7s_0K3j_X9S~I1 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 HIGX Date: Mon, 1 Feb 2016 19:51:32 -0800 Subject: [PATCH 113/233] Fix #691 by making sure the order of async calls completes properly so that the result set is filled BEFORE we try to serialize it. Signed-off-by: Chris Larsen --- src/core/RowKey.java | 1 + src/tsd/QueryRpc.java | 18 +++++++++--------- test/core/TestRowKey.java | 9 +++++---- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/core/RowKey.java b/src/core/RowKey.java index 0635b21a97..694ac119c5 100644 --- a/src/core/RowKey.java +++ b/src/core/RowKey.java @@ -98,6 +98,7 @@ public static byte[] rowKeyFromTSUID(final TSDB tsdb, final byte[] tsuid, System.arraycopy(tsuid, tsdb.metrics.width(), row, Const.SALT_WIDTH() + tsdb.metrics.width() + Const.TIMESTAMP_BYTES, tsuid.length - tsdb.metrics.width()); + RowKey.prefixKeyWithSalt(row); return row; } diff --git a/src/tsd/QueryRpc.java b/src/tsd/QueryRpc.java index 2c93eeef1e..bef1a64cf7 100644 --- a/src/tsd/QueryRpc.java +++ b/src/tsd/QueryRpc.java @@ -354,9 +354,9 @@ public String toString() { } } - final class FetchCB implements Callback> { + final class FetchCB implements Callback, ArrayList> { @Override - public Object call(final ArrayList dps) throws Exception { + public Deferred call(final ArrayList dps) throws Exception { synchronized(results) { for (final IncomingDataPoint dp : dps) { if (dp != null) { @@ -364,7 +364,7 @@ public Object call(final ArrayList dps) throws Exception { } } } - return null; + return Deferred.fromResult(null); } @Override public String toString() { @@ -377,8 +377,8 @@ public String toString() { * metric and/or tags. If matches were found, it fires off a number of * getLastPoint requests, adding the deferreds to the calls list */ - final class TSUIDQueryCB implements Callback> { - public Object call(final ByteMap tsuids) throws Exception { + final class TSUIDQueryCB implements Callback, ByteMap> { + public Deferred call(final ByteMap tsuids) throws Exception { if (tsuids == null || tsuids.isEmpty()) { return null; } @@ -389,8 +389,7 @@ public Object call(final ByteMap tsuids) throws Exception { data_query.getResolveNames(), data_query.getBackScan(), entry.getValue())); } - calls.add(Deferred.group(deferreds).addCallback(new FetchCB())); - return null; + return Deferred.group(deferreds).addCallbackDeferring(new FetchCB()); } @Override public String toString() { @@ -437,12 +436,13 @@ public String toString() { deferreds.add(tsuid_query.getLastPoint(data_query.getResolveNames(), data_query.getBackScan())); } else { - calls.add(tsuid_query.getLastWriteTimes().addCallback(new TSUIDQueryCB())); + calls.add(tsuid_query.getLastWriteTimes() + .addCallbackDeferring(new TSUIDQueryCB())); } } if (deferreds.size() > 0) { - calls.add(Deferred.group(deferreds).addCallback(new FetchCB())); + calls.add(Deferred.group(deferreds).addCallbackDeferring(new FetchCB())); } } diff --git a/test/core/TestRowKey.java b/test/core/TestRowKey.java index aea4ce34d2..59032a9f0c 100644 --- a/test/core/TestRowKey.java +++ b/test/core/TestRowKey.java @@ -148,21 +148,22 @@ public void rowKeyFromTSUIDMillis() throws Exception { public void rowKeyFromTSUIDSalted() throws Exception { PowerMockito.mockStatic(Const.class); PowerMockito.when(Const.SALT_WIDTH()).thenReturn(1); + PowerMockito.when(Const.SALT_BUCKETS()).thenReturn(2); final byte[] tsuid = { 0, 0, 1, 0, 0, 1, 0, 0, 2 }; - byte[] key = { 0, 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, 0, 0, 1, 0, 0, 2 }; + byte[] key = { 1, 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, 0, 0, 1, 0, 0, 2 }; assertArrayEquals(key, RowKey.rowKeyFromTSUID(tsdb, tsuid, 1356998400)); // zero timestamp - key = new byte[] { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2 }; + key = new byte[] { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2 }; assertArrayEquals(key, RowKey.rowKeyFromTSUID(tsdb, tsuid, 0)); // negative timestamp; honey badger don't care - key = new byte[] { 0, 0, 0, 1, -1, -21, 88, -128, 0, 0, 1, 0, 0, 2 }; + key = new byte[] { 1, 0, 0, 1, -1, -21, 88, -128, 0, 0, 1, 0, 0, 2 }; assertArrayEquals(key, RowKey.rowKeyFromTSUID(tsdb, tsuid, -1356998400)); PowerMockito.when(Const.SALT_WIDTH()).thenReturn(4); - key = new byte[] { 0, 0, 0, 0, 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + key = new byte[] { 0, 0, 0, 1, 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, 0, 0, 1, 0, 0, 2 }; assertArrayEquals(key, RowKey.rowKeyFromTSUID(tsdb, tsuid, 1356998400)); } From f99147fd03704654649103085f05d1a698ed4a5e Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Tue, 2 Feb 2016 19:15:01 -0800 Subject: [PATCH 114/233] Set tsd.query.allow_simultaneous_duplicates = true by default. This was causing some conflicts and confusion so we'll let folks enable it if they have problems with abuse. Signed-off-by: Chris Larsen --- src/utils/Config.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/Config.java b/src/utils/Config.java index 554891e0b1..f1ff0e03f1 100644 --- a/src/utils/Config.java +++ b/src/utils/Config.java @@ -500,7 +500,7 @@ protected void setDefaults() { default_map.put("tsd.core.uid.random_metrics", "false"); default_map.put("tsd.query.filter.expansion_limit", "4096"); default_map.put("tsd.query.skip_unresolved_tagvs", "false"); - default_map.put("tsd.query.allow_simultaneous_duplicates", "false"); + default_map.put("tsd.query.allow_simultaneous_duplicates", "true"); default_map.put("tsd.rtpublisher.enable", "false"); default_map.put("tsd.rtpublisher.plugin", ""); default_map.put("tsd.search.enable", "false"); From 60c2234c0bc8436aceee576c6ea4d3513db50676 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Thu, 4 Feb 2016 14:37:30 -0800 Subject: [PATCH 115/233] Fix #684 by allowing the max unsigned value to return the max SIGNED value (With a note) so that if someone does bump their UIDs to the maximum allowed it will properly display the stats (at least until Long.MAX_VALUE uids are assigned, then it will be funky.) Signed-off-by: Chris Larsen --- src/core/Internal.java | 16 +++++++++++----- test/core/TestInternal.java | 3 ++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/core/Internal.java b/src/core/Internal.java index e8d7867874..733dc04859 100644 --- a/src/core/Internal.java +++ b/src/core/Internal.java @@ -888,17 +888,23 @@ public static void createAndSetTSUIDFilter(final Scanner scanner, } /** - * Simple helper to calculate the max value for any width of long from 0 to 7 + * Simple helper to calculate the max value for any width of long from 0 to 8 * bytes. * @param width The width of the byte array we're comparing - * @return The maximum unsigned integer value on {@link width} bytes. + * @return The maximum unsigned integer value on {@link width} bytes. Note: + * If you ask for 8 bytes, it will return the max signed value. This is due + * to Java lacking unsigned integers... *sigh*. * @since 2.2 */ public static long getMaxUnsignedValueOnBytes(final int width) { - if (width < 0 || width > 7) { - throw new IllegalArgumentException("Width must be from 1 to 7 bytes: " + if (width < 0 || width > 8) { + throw new IllegalArgumentException("Width must be from 1 to 8 bytes: " + width); } - return ((long) 1 << width * Byte.SIZE) - 1; + if (width < 8) { + return ((long) 1 << width * Byte.SIZE) - 1; + } else { + return Long.MAX_VALUE; + } } } diff --git a/test/core/TestInternal.java b/test/core/TestInternal.java index 40a08f8192..c654074078 100644 --- a/test/core/TestInternal.java +++ b/test/core/TestInternal.java @@ -822,9 +822,10 @@ public void getMaxUnsignedValueOnBytes() throws Exception { assertEquals(1099511627775L, Internal.getMaxUnsignedValueOnBytes(5)); assertEquals(281474976710655L, Internal.getMaxUnsignedValueOnBytes(6)); assertEquals(72057594037927935L, Internal.getMaxUnsignedValueOnBytes(7)); + assertEquals(Long.MAX_VALUE, Internal.getMaxUnsignedValueOnBytes(8)); try { - Internal.getMaxUnsignedValueOnBytes(8); + Internal.getMaxUnsignedValueOnBytes(9); fail("Expected an IllegalArgumentException"); } catch (IllegalArgumentException e) { assertNotNull(e); From d0e877c06b22942a5ef642a61c4744f9e675e575 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Sun, 7 Feb 2016 23:17:44 -0800 Subject: [PATCH 116/233] Fixed typos in api/config/filters --- src/query/filter/TagVLiteralOrFilter.java | 4 ++-- src/query/filter/TagVNotLiteralOrFilter.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/query/filter/TagVLiteralOrFilter.java b/src/query/filter/TagVLiteralOrFilter.java index d80618e3be..f2ba5446ce 100644 --- a/src/query/filter/TagVLiteralOrFilter.java +++ b/src/query/filter/TagVLiteralOrFilter.java @@ -147,7 +147,7 @@ public String getType() { /** @return a string describing the filter */ public static String description() { return "Accepts one or more exact values and matches if the series contains " - + "any of them. Multiple values can be included and must be seperated " + + "any of them. Multiple values can be included and must be separated " + "by the | (pipe) character. The filter is case sensitive and will not " + "allow characters that TSDB does not allow at write time."; } @@ -196,7 +196,7 @@ public boolean equals(final Object obj) { /** @return a string describing the filter */ public static String description() { return "Accepts one or more exact values and matches if the series contains " - + "any of them. Multiple values can be included and must be seperated " + + "any of them. Multiple values can be included and must be separated " + "by the | (pipe) character. The filter is case insensitive and will not " + "allow characters that TSDB does not allow at write time."; } diff --git a/src/query/filter/TagVNotLiteralOrFilter.java b/src/query/filter/TagVNotLiteralOrFilter.java index c384697d67..78fae685ea 100644 --- a/src/query/filter/TagVNotLiteralOrFilter.java +++ b/src/query/filter/TagVNotLiteralOrFilter.java @@ -124,7 +124,7 @@ public int hashCode() { public static String description() { return "Accepts one or more exact values and matches if the series does NOT " + "contain any of them. Multiple values can be included and must be " - + "seperated by the | (pipe) character. The filter is case sensitive " + + "separated by the | (pipe) character. The filter is case sensitive " + "and will not allow characters that TSDB does not allow at write time."; } @@ -173,7 +173,7 @@ public boolean equals(final Object obj) { public static String description() { return "Accepts one or more exact values and matches if the series does NOT " + "contain any of them. Multiple values can be included and must be " - + "seperated by the | (pipe) character. The filter is case insensitive " + + "separated by the | (pipe) character. The filter is case insensitive " + "and will not allow characters that TSDB does not allow at write time."; } From 000e80c808ae448f2be68f14db36d7146a811fe3 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sat, 13 Feb 2016 12:54:27 -0800 Subject: [PATCH 117/233] Bump to AsyncHbase 1.7.1 release Signed-off-by: Chris Larsen --- third_party/hbase/asynchbase-1.7.0-20150910.030815-3.jar.md5 | 1 - third_party/hbase/asynchbase-1.7.1.jar.md5 | 1 + third_party/hbase/include.mk | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 third_party/hbase/asynchbase-1.7.0-20150910.030815-3.jar.md5 create mode 100644 third_party/hbase/asynchbase-1.7.1.jar.md5 diff --git a/third_party/hbase/asynchbase-1.7.0-20150910.030815-3.jar.md5 b/third_party/hbase/asynchbase-1.7.0-20150910.030815-3.jar.md5 deleted file mode 100644 index 9d3a066783..0000000000 --- a/third_party/hbase/asynchbase-1.7.0-20150910.030815-3.jar.md5 +++ /dev/null @@ -1 +0,0 @@ -84b8410ba9003ecadbeececb02943ee1 \ No newline at end of file diff --git a/third_party/hbase/asynchbase-1.7.1.jar.md5 b/third_party/hbase/asynchbase-1.7.1.jar.md5 new file mode 100644 index 0000000000..45ad0e9669 --- /dev/null +++ b/third_party/hbase/asynchbase-1.7.1.jar.md5 @@ -0,0 +1 @@ +f236854721eac6d40b6710ec7d59f4a8 diff --git a/third_party/hbase/include.mk b/third_party/hbase/include.mk index 0e25495aba..bb25220311 100644 --- a/third_party/hbase/include.mk +++ b/third_party/hbase/include.mk @@ -13,9 +13,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see . -ASYNCHBASE_VERSION := 1.7.1-20151004.015637-1 +ASYNCHBASE_VERSION := 1.7.1 ASYNCHBASE := third_party/hbase/asynchbase-$(ASYNCHBASE_VERSION).jar -ASYNCHBASE_BASE_URL := https://oss.sonatype.org/content/repositories/snapshots/org/hbase/asynchbase/1.7.1-SNAPSHOT/ +ASYNCHBASE_BASE_URL := http://central.maven.org/maven2/org/hbase/asynchbase/$(ASYNCHBASE_VERSION) $(ASYNCHBASE): $(ASYNCHBASE).md5 set dummy "$(ASYNCHBASE_BASE_URL)" "$(ASYNCHBASE)"; shift; $(FETCH_DEPENDENCY) From cae191d6eabd4b81cd34b9fdc6572866756cba50 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Fri, 20 Nov 2015 17:06:22 -0800 Subject: [PATCH 118/233] Add a try/catch to the FSCK utility to log problems found when printing row information. Signed-off-by: Chris Larsen --- src/tools/Fsck.java | 62 ++++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/src/tools/Fsck.java b/src/tools/Fsck.java index 45dde4539f..6295ff32f4 100644 --- a/src/tools/Fsck.java +++ b/src/tools/Fsck.java @@ -643,36 +643,40 @@ private void fsckDataPoints(final Map> datapoints) dp_index++) { duplicates.getAndIncrement(); DP dp = time_map.getValue().get(dp_index); - final byte flags = (byte)Internal.getFlagsFromQualifier(dp.kv.qualifier()); - buf.append(" ") - .append("write time: (") - .append(dp.kv.timestamp()) - .append(" - ") - .append(new Date(dp.kv.timestamp())) - .append(") ") - .append(" compacted: (") - .append(dp.compacted) - .append(") qualifier: ") - .append(Arrays.toString(dp.kv.qualifier())) - .append(" value: ") - .append(Internal.isFloat(dp.kv.qualifier()) ? - Internal.extractFloatingPointValue(dp.value(), 0, flags) : - Internal.extractIntegerValue(dp.value(), 0, flags)) - .append("\n"); - unique_columns.put(dp.kv.qualifier(), dp.kv.value()); - if (options.fix() && options.resolveDupes()) { - if (compact_row) { - // Scheduled for deletion by compaction. - duplicates_fixed_comp.getAndIncrement(); - } else if (!dp.compacted) { - LOG.debug("Removing duplicate data point: " + dp.kv); - tsdb.getClient().delete( - new DeleteRequest( - tsdb.dataTable(), dp.kv.key(), dp.kv.family(), dp.qualifier() - ) - ); - duplicates_fixed.getAndIncrement(); + try { + final byte flags = (byte)Internal.getFlagsFromQualifier(dp.kv.qualifier()); + buf.append(" ") + .append("write time: (") + .append(dp.kv.timestamp()) + .append(" - ") + .append(new Date(dp.kv.timestamp())) + .append(") ") + .append(" compacted: (") + .append(dp.compacted) + .append(") qualifier: ") + .append(Arrays.toString(dp.kv.qualifier())) + .append(" value: ") + .append(Internal.isFloat(dp.kv.qualifier()) ? + Internal.extractFloatingPointValue(dp.value(), 0, flags) : + Internal.extractIntegerValue(dp.value(), 0, flags)) + .append("\n"); + unique_columns.put(dp.kv.qualifier(), dp.kv.value()); + if (options.fix() && options.resolveDupes()) { + if (compact_row) { + // Scheduled for deletion by compaction. + duplicates_fixed_comp.getAndIncrement(); + } else if (!dp.compacted) { + LOG.debug("Removing duplicate data point: " + dp.kv); + tsdb.getClient().delete( + new DeleteRequest( + tsdb.dataTable(), dp.kv.key(), dp.kv.family(), dp.qualifier() + ) + ); + duplicates_fixed.getAndIncrement(); + } } + } catch (Exception e) { + LOG.error("Unexpected exception processing DP: " + dp); } } if (options.lastWriteWins()) { From d9d60d1991daeb294ebff0260eb266724fbb0b0f Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sat, 13 Feb 2016 16:39:06 -0800 Subject: [PATCH 119/233] Rework the QueryStats before release by adding a bunch of timing around the scanner, HBase, serialization, etc. The class was reworked and the API is different but I think more useful and expandable. Signed-off-by: Chris Larsen --- src/core/SaltScanner.java | 143 ++++- src/core/TsdbQuery.java | 115 +++- src/stats/QueryStats.java | 797 ++++++++++++++++++++++----- src/tsd/AbstractHttpQuery.java | 73 ++- src/tsd/HttpJsonSerializer.java | 70 ++- src/tsd/HttpSerializer.java | 3 +- src/tsd/QueryRpc.java | 95 ++-- src/tsd/RpcHandler.java | 1 + src/tsd/StatsRpc.java | 2 +- src/utils/DateTime.java | 37 ++ test/stats/TestQueryStats.java | 203 ++++--- test/tsd/NettyMocks.java | 11 + test/tsd/TestHttpJsonSerializer.java | 38 +- test/tsd/TestQueryRpc.java | 3 + test/utils/TestDateTime.java | 29 + 15 files changed, 1288 insertions(+), 332 deletions(-) diff --git a/src/core/SaltScanner.java b/src/core/SaltScanner.java index 4acfc39ac9..9498284e00 100644 --- a/src/core/SaltScanner.java +++ b/src/core/SaltScanner.java @@ -25,7 +25,10 @@ import net.opentsdb.meta.Annotation; import net.opentsdb.query.filter.TagVFilter; +import net.opentsdb.stats.QueryStats; +import net.opentsdb.stats.QueryStats.QueryStat; import net.opentsdb.uid.UniqueId; +import net.opentsdb.utils.DateTime; import org.hbase.async.Bytes.ByteMap; import org.hbase.async.DeleteRequest; @@ -84,6 +87,13 @@ public class SaltScanner { /** The TSDB to which we belong */ private final TSDB tsdb; + /** A stats object associated with the sub query used for storing stats + * about scanner operations. */ + private final QueryStats query_stats; + + /** Index of the sub query in the main query list */ + private final int query_index; + /** A counter used to determine how many scanners are still running */ private AtomicInteger completed_tasks = new AtomicInteger(); @@ -117,7 +127,7 @@ public SaltScanner(final TSDB tsdb, final byte[] metric, final List scanners, final TreeMap spans, final List filters) { - this(tsdb, metric, scanners, spans, filters, false); + this(tsdb, metric, scanners, spans, filters, false, null, 0); } /** @@ -129,6 +139,8 @@ public SaltScanner(final TSDB tsdb, final byte[] metric, * @param spans The span map to store results in * @param delete Whether or not to delete the queried data * @param filters A list of filters for processing + * @param query_stats A stats object for tracking timing + * @param query_index The index of the sub query in the main query list * @throws IllegalArgumentException if any required data was missing or * we had invalid parameters. */ @@ -136,7 +148,9 @@ public SaltScanner(final TSDB tsdb, final byte[] metric, final List scanners, final TreeMap spans, final List filters, - final boolean delete) { + final boolean delete, + final QueryStats query_stats, + final int query_index) { if (Const.SALT_WIDTH() < 1) { throw new IllegalArgumentException( "Salting is disabled. Use the regular scanner"); @@ -173,6 +187,8 @@ public SaltScanner(final TSDB tsdb, final byte[] metric, this.tsdb = tsdb; this.filters = filters; this.delete = delete; + this.query_stats = query_stats; + this.query_index = query_index; } /** @@ -184,8 +200,9 @@ public SaltScanner(final TSDB tsdb, final byte[] metric, */ public Deferred> scan() { start_time = System.currentTimeMillis(); + int i = 0; for (final Scanner scanner: scanners) { - new ScannerCB(scanner).scan(); + new ScannerCB(scanner, i++).scan(); } return results; } @@ -208,6 +225,7 @@ private void mergeAndReturnResults() { } // Merge sorted spans together + final long merge_start = DateTime.nanoTime(); for (final List kvs : kv_map.values()) { if (kvs == null || kvs.isEmpty()) { LOG.warn("Found a key value list that was null or empty"); @@ -260,6 +278,10 @@ private void mergeAndReturnResults() { } } + if (query_stats != null) { + query_stats.addStat(query_index, QueryStat.SCANNER_MERGE_TIME, + (DateTime.nanoTime() - merge_start)); + } if (LOG.isDebugEnabled()) { LOG.debug("Scanning completed in " + (hbase_time - start_time) + " ms, " + rows + " rows, and stored in " + spans.size() + " spans"); @@ -280,14 +302,29 @@ private void mergeAndReturnResults() { final class ScannerCB implements Callback>> { private final Scanner scanner; + private final int index; private final List kvs = new ArrayList(); private final ByteMap> annotations = new ByteMap>(); private final Set skips = new HashSet(); private final Set keepers = new HashSet(); - public ScannerCB(final Scanner scanner) { + private long scanner_start = -1; + /** nanosecond timestamps */ + private long fetch_start = 0; // reset each time we send an RPC to HBase + private long fetch_time = 0; // cumulation of time waiting on HBase + private long uid_resolve_time = 0; // cumulation of time resolving UIDs + private long uids_resolved = 0; + private long compaction_time = 0; // cumulation of time compacting + private long dps_post_filter = 0; + private long rows_post_filter = 0; + + public ScannerCB(final Scanner scanner, final int index) { this.scanner = scanner; + this.index = index; + if (query_stats != null) { + query_stats.addScannerId(query_index, index, scanner.toString()); + } } /** Error callback that will capture an exception from AsyncHBase and store @@ -297,7 +334,7 @@ class ErrorCb implements Callback { @Override public Object call(final Exception e) throws Exception { LOG.error("Scanner " + scanner + " threw an exception", e); - scanner.close(); + close(false); handleException(e); return null; } @@ -310,6 +347,10 @@ public Object call(final Exception e) throws Exception { * found */ public Object scan() { + if (scanner_start < 0) { + scanner_start = DateTime.nanoTime(); + } + fetch_start = DateTime.nanoTime(); return scanner.nextRows().addCallback(this).addErrback(new ErrorCb()); } @@ -322,9 +363,17 @@ public Object scan() { public Object call(final ArrayList> rows) throws Exception { try { + fetch_time += DateTime.nanoTime() - fetch_start; if (rows == null) { - scanner.close(); - validateAndTriggerCallback(kvs, annotations); + close(true); + return null; + } else if (exception != null) { + close(false); + // don't need to handleException here as it's already taken care of + // due to the fact that exception was set. + if (LOG.isDebugEnabled()) { + LOG.debug("Closing scanner as there was an exception: " + scanner); + } return null; } @@ -336,7 +385,7 @@ public Object call(final ArrayList> rows) for (final ArrayList row : rows) { final byte[] key = row.get(0).key(); if (RowKey.rowKeyContainsMetric(metric, key) != 0) { - scanner.close(); + close(false); handleException(new IllegalDataException( "HBase returned a row that doesn't match" + " our scanner (" + scanner + ")! " + row + " does not start" @@ -359,6 +408,8 @@ public Object call(final ArrayList> rows) continue; } if (!keepers.contains(tsuid)) { + final long uid_start = DateTime.nanoTime(); + /** CB to called after all of the UIDs have been resolved */ class MatchCB implements Callback> { @Override @@ -383,6 +434,8 @@ class GetTagsCB implements @Override public Deferred> call( final Map tags) throws Exception { + uid_resolve_time += (DateTime.nanoTime() - uid_start); + uids_resolved += tags.size(); final List> matches = new ArrayList>(filters.size()); @@ -420,7 +473,7 @@ public Object call(final ArrayList group) throws Exception { } } catch (final RuntimeException e) { LOG.error("Unexpected exception on scanner " + this, e); - scanner.close(); + close(false); handleException(e); return null; } @@ -447,11 +500,67 @@ void processRow(final byte[] key, final ArrayList row) { final KeyValue compacted; // let IllegalDataExceptions bubble up so the handler above can close // the scanner - compacted = tsdb.compact(row, notes); + final long compaction_start = DateTime.nanoTime(); + try { + compacted = tsdb.compact(row, notes); + } catch (IllegalDataException idex) { + compaction_time += (DateTime.nanoTime() - compaction_start); + close(false); + handleException(idex); + return; + } + compaction_time += (DateTime.nanoTime() - compaction_start); if (compacted != null) { // Can be null if we ignored all KVs. kvs.add(compacted); } } + + /** + * Closes the scanner and sets the various stats after filtering + * @param ok Whether or not the scanner closed with an exception or + * closed due to natural causes (e.g. ran out of data or we wanted to stop + * it early) + */ + void close(final boolean ok) { + scanner.close(); + + if (query_stats != null) { + query_stats.addScannerStat(query_index, index, QueryStat.SCANNER_TIME, + DateTime.nanoTime() - scanner_start); + + // Scanner Stats + /* Uncomment when AsyncHBase has this feature: + query_stats.addScannerStat(query_index, index, + QueryStat.ROWS_FROM_STORAGE, scanner.getRowsFetched()); + query_stats.addScannerStat(query_index, index, + QueryStat.COLUMNS_FROM_STORAGE, scanner.getColumnsFetched()); + query_stats.addScannerStat(query_index, index, + QueryStat.BYTES_FROM_STORAGE, scanner.getBytesFetched()); */ + query_stats.addScannerStat(query_index, index, + QueryStat.HBASE_TIME, fetch_time); + query_stats.addScannerStat(query_index, index, + QueryStat.SUCCESSFUL_SCAN, ok ? 1 : 0); + + // Post Scan stats + /* TODO - fix up/add these counters + query_stats.addScannerStat(query_index, index, + QueryStat.ROWS_POST_FILTER, rows_post_filter); + query_stats.addScannerStat(query_index, index, + QueryStat.DPS_POST_FILTER, dps_post_filter); */ + query_stats.addScannerStat(query_index, index, + QueryStat.SCANNER_UID_TO_STRING_TIME, uid_resolve_time); + query_stats.addScannerStat(query_index, index, + QueryStat.UID_PAIRS_RESOLVED, uids_resolved); + query_stats.addScannerStat(query_index, index, + QueryStat.COMPACTION_TIME, compaction_time); + } + + if (ok && exception == null) { + validateAndTriggerCallback(kvs, annotations); + } else { + completed_tasks.incrementAndGet(); + } + } } /** @@ -493,10 +602,19 @@ private void validateAndTriggerCallback(final List kvs, */ private void handleException(final Exception e) { // make sure only one scanner can set the exception + completed_tasks.incrementAndGet(); if (exception == null) { synchronized (this) { if (exception == null) { exception = e; + // fail once and fast on the first scanner to throw an exception + try { + mergeAndReturnResults(); + } catch (Exception ex) { + LOG.error("Failed merging and returning results, " + + "calling back with exception", ex); + results.callback(ex); + } } else { // TODO - it would be nice to close and cancel the other scanners but // for now we have to wait for them to finish and/or throw exceptions. @@ -504,10 +622,5 @@ private void handleException(final Exception e) { } } } - - final int tasks = completed_tasks.incrementAndGet(); - if (tasks >= Const.SALT_BUCKETS()) { - results.callback(exception); - } } } diff --git a/src/core/TsdbQuery.java b/src/core/TsdbQuery.java index 7b060a44ed..5d96c4e1c3 100644 --- a/src/core/TsdbQuery.java +++ b/src/core/TsdbQuery.java @@ -41,6 +41,8 @@ import net.opentsdb.query.QueryUtil; import net.opentsdb.query.filter.TagVFilter; import net.opentsdb.stats.Histogram; +import net.opentsdb.stats.QueryStats; +import net.opentsdb.stats.QueryStats.QueryStat; import net.opentsdb.uid.NoSuchUniqueId; import net.opentsdb.uid.NoSuchUniqueName; import net.opentsdb.uid.UniqueId; @@ -71,6 +73,9 @@ final class TsdbQuery implements Query { /** The TSDB we belong to. */ private final TSDB tsdb; + + /** The time, in ns, when we start scanning for data **/ + private long scan_start_time; /** Value used for timestamps that are uninitialized. */ private static final int UNSET = -1; @@ -132,6 +137,9 @@ final class TsdbQuery implements Query { /** Tag value filters to apply post scan */ private List filters; + /** An object for storing stats in regarding the query. May be null */ + private QueryStats query_stats; + /** Constructor. */ public TsdbQuery(final TSDB tsdb) { this.tsdb = tsdb; @@ -313,6 +321,7 @@ public Deferred configureFromQuery(final TSQuery query, setEndTime(query.endTime()); setDelete(query.getDelete()); query_index = index; + query_stats = query.getQueryStats(); // set common options aggregator = sub_query.aggregator(); @@ -541,10 +550,12 @@ private Deferred> findSpans() throws HBaseException { for (int i = 0; i < Const.SALT_BUCKETS(); i++) { scanners.add(getScanner(i)); } - return new SaltScanner(tsdb, metric, scanners, spans, scanner_filters) - .scan(); + scan_start_time = DateTime.nanoTime(); + return new SaltScanner(tsdb, metric, scanners, spans, scanner_filters, + delete, query_stats, query_index).scan(); } + scan_start_time = DateTime.nanoTime(); final Scanner scanner = getScanner(); final Deferred> results = new Deferred>(); @@ -561,11 +572,17 @@ final class ScannerCB implements Callback skips = new HashSet(); private final Set keepers = new HashSet(); + private final int index = 0; // only used for salted scanners + /** nanosecond timestamps */ + private long fetch_start = 0; // reset each time we send an RPC to HBase + private long fetch_time = 0; // cumulation of time waiting on HBase + private long uid_resolve_time = 0; // cumulation of time resolving UIDs + private long uids_resolved = 0; + private long compaction_time = 0; // cumulation of time compacting /** Error callback that will capture an exception from AsyncHBase and store * it so we can bubble it up to the caller. @@ -574,8 +591,7 @@ class ErrorCB implements Callback { @Override public Object call(final Exception e) throws Exception { LOG.error("Scanner " + scanner + " threw an exception", e); - scanner.close(); - results.callback(e); + close(e); return null; } } @@ -587,7 +603,7 @@ public Object call(final Exception e) throws Exception { * found */ public Object scan() { - starttime = System.nanoTime(); + fetch_start = DateTime.nanoTime(); return scanner.nextRows().addCallback(this).addErrback(new ErrorCB()); } @@ -599,23 +615,18 @@ public Object scan() { @Override public Object call(final ArrayList> rows) throws Exception { - hbase_time += (System.nanoTime() - starttime) / 1000000; + fetch_time += DateTime.nanoTime() - fetch_start; try { if (rows == null) { - hbase_time += (System.nanoTime() - starttime) / 1000000; - scanlatency.add(hbase_time); + scanlatency.add((int)DateTime.msFromNano(fetch_time)); LOG.info(TsdbQuery.this + " matched " + nrows + " rows in " + - spans.size() + " spans in " + hbase_time + "ms"); - if (nrows < 1 && !seenAnnotation) { - results.callback(null); - } else { - results.callback(spans); - } - scanner.close(); + spans.size() + " spans in " + DateTime.msFromNano(fetch_time) + "ms"); + close(null); return null; } - if (timeout > 0 && hbase_time > timeout) { + if (timeout > 0 && DateTime.msFromNanoDiff( + DateTime.nanoTime(), scanner_start) > timeout) { throw new InterruptedException("Query timeout exceeded!"); } @@ -649,6 +660,8 @@ public Object call(final ArrayList> rows) continue; } if (!keepers.contains(tsuid)) { + final long uid_start = DateTime.nanoTime(); + /** CB to called after all of the UIDs have been resolved */ class MatchCB implements Callback> { @Override @@ -673,6 +686,8 @@ class GetTagsCB implements @Override public Deferred> call( final Map tags) throws Exception { + uid_resolve_time += (DateTime.nanoTime() - uid_start); + uids_resolved += tags.size(); final List> matches = new ArrayList>(scanner_filters.size()); @@ -709,8 +724,7 @@ public Object call(final ArrayList group) throws Exception { return scan(); } } catch (Exception e) { - scanner.close(); - results.callback(e); + close(e); return null; } } @@ -731,15 +745,60 @@ void processRow(final byte[] key, final ArrayList row) { datapoints = new Span(tsdb); spans.put(key, datapoints); } + final long compaction_start = DateTime.nanoTime(); final KeyValue compacted = tsdb.compact(row, datapoints.getAnnotations()); + compaction_time += (DateTime.nanoTime() - compaction_start); seenAnnotation |= !datapoints.getAnnotations().isEmpty(); if (compacted != null) { // Can be null if we ignored all KVs. datapoints.addRow(compacted); ++nrows; } } - } + + void close(final Exception e) { + scanner.close(); + + if (query_stats != null) { + query_stats.addScannerStat(query_index, index, + QueryStat.SCANNER_TIME, DateTime.nanoTime() - scan_start_time); + + // Scanner Stats + /* Uncomment when AsyncHBase has this feature: + query_stats.addScannerStat(query_index, index, + QueryStat.ROWS_FROM_STORAGE, scanner.getRowsFetched()); + query_stats.addScannerStat(query_index, index, + QueryStat.COLUMNS_FROM_STORAGE, scanner.getColumnsFetched()); + query_stats.addScannerStat(query_index, index, + QueryStat.BYTES_FROM_STORAGE, scanner.getBytesFetched()); */ + query_stats.addScannerStat(query_index, index, + QueryStat.HBASE_TIME, fetch_time); + query_stats.addScannerStat(query_index, index, + QueryStat.SUCCESSFUL_SCAN, e == null ? 1 : 0); + + // Post Scan stats + /* TODO - fix up/add these counters + query_stats.addScannerStat(query_index, index, + QueryStat.ROWS_POST_FILTER, rows_post_filter); + query_stats.addScannerStat(query_index, index, + QueryStat.DPS_POST_FILTER, dps_post_filter); */ + query_stats.addScannerStat(query_index, index, + QueryStat.SCANNER_UID_TO_STRING_TIME, uid_resolve_time); + query_stats.addScannerStat(query_index, index, + QueryStat.UID_PAIRS_RESOLVED, uids_resolved); + query_stats.addScannerStat(query_index, index, + QueryStat.COMPACTION_TIME, compaction_time); + } + + if (e != null) { + results.callback(e); + } else if (nrows < 1 && !seenAnnotation) { + results.callback(null); + } else { + results.callback(spans); + } + } + } new ScannerCB().scan(); return results; @@ -761,7 +820,15 @@ private class GroupByAndAggregateCB implements */ @Override public DataPoints[] call(final TreeMap spans) throws Exception { + if (query_stats != null) { + query_stats.addStat(query_index, QueryStat.QUERY_SCAN_TIME, + (System.nanoTime() - TsdbQuery.this.scan_start_time)); + } + if (spans == null || spans.size() <= 0) { + if (query_stats != null) { + query_stats.addStat(query_index, QueryStat.GROUP_BY_TIME, 0); + } return NO_RESULT; } if (group_bys == null) { @@ -775,6 +842,9 @@ public DataPoints[] call(final TreeMap spans) throws Exception { aggregator, sample_interval_ms, downsampler, query_index, fill_policy); + if (query_stats != null) { + query_stats.addStat(query_index, QueryStat.GROUP_BY_TIME, 0); + } return new SpanGroup[] { group }; } @@ -831,6 +901,9 @@ public DataPoints[] call(final TreeMap spans) throws Exception { //for (final Map.Entry entry : groups) { // LOG.info("group for " + Arrays.toString(entry.getKey()) + ": " + entry.getValue()); //} + if (query_stats != null) { + query_stats.addStat(query_index, QueryStat.GROUP_BY_TIME, 0); + } return groups.values().toArray(new SpanGroup[groups.size()]); } } diff --git a/src/stats/QueryStats.java b/src/stats/QueryStats.java index 7f1892f86f..757a9bdca9 100644 --- a/src/stats/QueryStats.java +++ b/src/stats/QueryStats.java @@ -15,8 +15,12 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import org.jboss.netty.handler.codec.http.HttpResponseStatus; @@ -27,10 +31,12 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; +import net.opentsdb.core.Const; import net.opentsdb.core.QueryException; import net.opentsdb.core.TSQuery; import net.opentsdb.utils.DateTime; import net.opentsdb.utils.JSON; +import net.opentsdb.utils.Pair; /** * This class stores information about OpenTSDB queries executed through the @@ -44,17 +50,21 @@ * The cache will store each query based on the combination of the client, query * and the result code. If the same query was executed multiple times then it * will increment the "executed" counter for the query in the cache. + * + * NOTE: Record everything in nano seconds, then convert to floating millis for + * serialization. * @since 2.2 */ public class QueryStats { private static final Logger LOG = LoggerFactory.getLogger(QueryStats.class); + private static final Logger QUERY_LOG = LoggerFactory.getLogger("QueryLog"); /** Determines how many query stats to keep in the cache */ private static int COMPLETED_QUERY_CACHE_SIZE = 256; /** Whether or not to allow duplicate queries from the same endpoint to * run simultaneously. */ - private static boolean ENABLE_DUPLICATES = false; + private static boolean ENABLE_DUPLICATES = true; /** Stores queries currently executing. If a thread doesn't call into * markComplete then it's possible for this map to fill up. @@ -67,51 +77,164 @@ public class QueryStats { private static Cache completed_queries = CacheBuilder.newBuilder().maximumSize(COMPLETED_QUERY_CACHE_SIZE).build(); - /** Start time for the query. Can be set post construction if necessary */ - private final long query_start; + /** Start time for the query in nano seconds. Can be set post construction + * if necessary */ + private final long query_start_ns; + + /** Start timestamp for the query in millis for printing */ + private final long query_start_ms; + + /** When the query was marked completed in nanoseconds */ + private long query_completed_ts; /** The remote address as :, may be ipv6 */ private final String remote_address; /** The TSQuery object that contains the query specification */ private final TSQuery query; - - /** Amount of time taken for the query to complete, set on {@link markComplete} */ - private long time_total; - /** Time it took to retrieve data from storage in ms*/ - private long time_storage; + /** HTTP response when the query was completed, either successfully or failed */ + private HttpResponseStatus response; - /** Time it took to aggregate over the data */ - private long time_aggregation; + /** Set if the query terminated with an exception */ + private Throwable exception; - /** Time it took to serialize the data. Includes aggregation time and tag - * lookups */ - private long time_serialization; + /** How many times this exact query was executed. Only updated on completion */ + private long executed; - /** Number of data points emitted, NOT the number of data points fetched */ - private long size; + /** The users (if known) who executed this query (could be pulled from a header) */ + private String user; - /** Total number of data points fetched from storage */ - private long aggregated_size; + /** Stats for the entire query */ + private final Map overall_stats; - /** HTTP response when the query was completed, either successfully or failed */ - private HttpResponseStatus response; + /** Hold a list of stats for the sub queries */ + private final Map> query_stats; - /** How many times this exact query was executed. Only updated on completion */ - private long executed; + /** Holds a list of stats for each scanner */ + private final Map>> scanner_stats; - /** A possible exception if thrown when this query completes */ - private Throwable exception; + /** Hold a list of the region servers encountered for each scanner */ + private final Map>> scanner_servers; + + /** Holds a lis tof the scanner IDs for each scanner */ + private final Map> scanner_ids; + + /** Holds a copy of the headers from the request */ + private final Map headers; + + /** Whether or not the data was successfully sent to the client */ + private boolean sent_to_client; + + /** + * A list of statistics surrounding individual queries + */ + public enum QueryStat { + // Query Setup stats + STRING_TO_UID_TIME ("stringToUidTime", true), + + // Storage stats + COLUMNS_FROM_STORAGE ("columnsFromStorage", false), + ROWS_FROM_STORAGE ("rowsFromStorage", false), + BYTES_FROM_STORAGE ("bytesFromStorage", false), + SUCCESSFUL_SCAN ("successfulScan", false), + + // Single Scanner stats + DPS_POST_FILTER ("dpsPostFilter", false), + ROWS_POST_FILTER ("rowsPostFilter", false), + SCANNER_UID_TO_STRING_TIME ("scannerUidToStringTime", true), + COMPACTION_TIME ("compactionTime", true), + HBASE_TIME ("hbaseTime", true), + UID_PAIRS_RESOLVED ("uidPairsResolved", false), + SCANNER_TIME ("scannerTime", true), + + // Overall Salt Scanner stats + SCANNER_MERGE_TIME ("saltScannerMergeTime", true), + + // Post Scan stats + QUERY_SCAN_TIME ("queryScanTime", true), + GROUP_BY_TIME ("groupByTime", true), + + // Serialization time stats + UID_TO_STRING_TIME ("uidToStringTime", true), + AGGREGATED_SIZE ("emittedDPs", false), + NAN_DPS ("nanDPs", false), + AGGREGATION_TIME ("aggregationTime", true), + SERIALIZATION_TIME ("serializationTime", true), + + // Final stats + PROCESSING_PRE_WRITE_TIME ("processingPreWriteTime", true), + TOTAL_TIME ("totalTime", true), + + // MAX and Agg Times + MAX_HBASE_TIME ("maxHBaseTime", true), + AVG_HBASE_TIME ("avgHBaseTime", true), + MAX_SALT_SCANNER_TIME ("maxScannerTime", true), + AVG_SALT_SCANNER_TIME ("avgScannerTime", true), + MAX_UID_TO_STRING ("maxUidToStringTime", true), + AVG_UID_TO_STRING ("avgUidToStringTime", true), + MAX_COMPACTION_TIME ("maxCompactionTime", true), + AVG_COMPACTION_TIME ("avgCompactionTime", true), + MAX_SCANNER_UID_TO_STRING_TIME ("maxScannerUidtoStringTime", true), + AVG_SCANNER_UID_TO_STRING_TIME ("avgScannerUidToStringTime", true), + MAX_SCANNER_MERGE_TIME ("maxSaltScannerMergeTime", true), + AVG_SCANNER_MERGE_TIME ("avgSaltScannerMergeTime", true), + MAX_SCAN_TIME ("maxQueryScanTime", true), + AVG_SCAN_TIME ("avgQueryScanTime", true), + MAX_AGGREGATION_TIME ("maxAggregationTime", true), + AVG_AGGREGATION_TIME ("avgAggregationTime", true), + MAX_SERIALIZATION_TIME ("maxSerializationTime", true), + AVG_SERIALIZATION_TIME ("avgSerializationTime", true) + ; + + /** The serializable name for this enum */ + private final String stat_name; + /** Whether or not the stat is time based */ + private final boolean is_time; + + private QueryStat(final String stat_name, final boolean is_time) { + this.stat_name = stat_name; + this.is_time = is_time; + } + + @Override + public String toString() { + return stat_name; + } + } + + // always AVG, MAX in the pair order + static final Map> AGG_MAP = + new HashMap>(); + static { + AGG_MAP.put(QueryStat.HBASE_TIME, new Pair( + QueryStat.AVG_HBASE_TIME, QueryStat.MAX_HBASE_TIME)); + AGG_MAP.put(QueryStat.SCANNER_TIME, new Pair( + QueryStat.AVG_SALT_SCANNER_TIME, QueryStat.MAX_HBASE_TIME)); + AGG_MAP.put(QueryStat.UID_TO_STRING_TIME, new Pair( + QueryStat.MAX_UID_TO_STRING, QueryStat.MAX_UID_TO_STRING)); + AGG_MAP.put(QueryStat.SCANNER_UID_TO_STRING_TIME, new Pair( + QueryStat.MAX_SCANNER_UID_TO_STRING_TIME, + QueryStat.AVG_SCANNER_UID_TO_STRING_TIME)); + AGG_MAP.put(QueryStat.QUERY_SCAN_TIME, new Pair( + QueryStat.MAX_SCAN_TIME, QueryStat.AVG_SCAN_TIME)); + AGG_MAP.put(QueryStat.AGGREGATION_TIME, new Pair( + QueryStat.MAX_AGGREGATION_TIME, QueryStat.AVG_AGGREGATION_TIME)); + AGG_MAP.put(QueryStat.SERIALIZATION_TIME, new Pair( + QueryStat.MAX_SERIALIZATION_TIME, + QueryStat.AVG_SERIALIZATION_TIME)); + } /** * Default CTor * @param remote_address Remote address of the client * @param query Query being executed + * @param headers The HTTP headers passed with the query * @throws QueryException if the exact query is already running, e.g if the * client submitted the same query twice */ - public QueryStats(final String remote_address, final TSQuery query) { + public QueryStats(final String remote_address, final TSQuery query, + final Map headers) { if (remote_address == null || remote_address.isEmpty()) { throw new IllegalArgumentException("Remote address was null or empty"); } @@ -120,8 +243,16 @@ public QueryStats(final String remote_address, final TSQuery query) { } this.remote_address = remote_address; this.query = query; + this.headers = headers; // can be null executed = 1; - query_start = DateTime.currentTimeMillis(); + query_start_ns = DateTime.nanoTime(); + query_start_ms = DateTime.currentTimeMillis(); + overall_stats = new HashMap(); + query_stats = new ConcurrentHashMap>(1); + scanner_stats = new ConcurrentHashMap>>(1); + scanner_servers = new ConcurrentHashMap>>(1); + scanner_ids = new ConcurrentHashMap>(1); if (LOG.isDebugEnabled()) { LOG.debug("New query for remote " + remote_address + " with hash " + hashCode() + " on thread " + Thread.currentThread().getId()); @@ -134,22 +265,27 @@ public QueryStats(final String remote_address, final TSQuery query) { throw new QueryException("Query is already executing for endpoint: " + remote_address); } - } else { - if (LOG.isDebugEnabled()) { - LOG.debug("Successfully put new query for remote " + remote_address + - " with hash " + hashCode() + " on thread " + - Thread.currentThread().getId() + " w q " + query.toString()); - } } + if (LOG.isDebugEnabled()) { + LOG.debug("Successfully put new query for remote " + remote_address + + " with hash " + hashCode() + " on thread " + + Thread.currentThread().getId() + " w q " + query.toString()); + } + LOG.info("Executing new query=" + JSON.serializeToString(this)); } /** * Returns the hash based on the remote address and the query */ + @Override public int hashCode() { - return Objects.hashCode(remote_address, query.hashCode()); + return remote_address.hashCode() ^ query.hashCode(); } + /** + * Equals is based solely on the endpoint and the original query + */ + @Override public boolean equals(final Object obj) { if (obj == null) { return false; @@ -167,53 +303,61 @@ public boolean equals(final Object obj) { @Override public String toString() { - final StringBuilder buf = new StringBuilder(256); - buf.append("remote=") - .append(remote_address) - .append(", query=") - .append(query) - .append(", start=") - .append(query_start) - .append(", exception=") - .append(exception == null ? "null" : exception.getMessage()); - return buf.toString(); + // have to hack it to get the details. By default we dump just the highest + // level of stats. + final Map details = new HashMap(); + details.put("queryStartTimestamp", getQueryStartTimestamp()); + details.put("queryCompletedTimestamp", getQueryCompletedTimestamp()); + details.put("exception", getException()); + details.put("httpResponse", getHttpResponse()); + details.put("numRunningQueries", getNumRunningQueries()); + details.put("query", getQuery()); + details.put("user", getUser()); + details.put("requestHeaders", getRequestHeaders()); + details.put("executed", getExecuted()); + details.put("stats", getStats(true, true)); + return JSON.serializeToString(details); } /** - * Marks a query as completed successfully with the 200 HTTP response code. + * Marks a query as completed successfully with the 200 HTTP response code + * without an exception. * Moves it from the running map to the cache, updating the cache if it already * existed. */ - public void markComplete() { - markComplete(HttpResponseStatus.OK, null); + public void markSerializationSuccessful() { + markSerialized(HttpResponseStatus.OK, null); } /** - * Marks a query as completed with the given HTTP code and moves it from the - * running map to the cache, updating the cache if it already existed. - * @param response The HttpStatus code to store - * @param exception An optional exception + * Marks a query as completed with the given HTTP code with exception and + * moves it from the running map to the cache, updating the cache if it + * already existed. + * @param response The HTTP response to log + * @param exception The exception thrown */ - public void markComplete(final HttpResponseStatus response, + public void markSerialized(final HttpResponseStatus response, final Throwable exception) { this.exception = exception; - LOG.debug("Marking query as complete for " + remote_address + " with hash " + - hashCode() + " on thread " + Thread.currentThread().getId() + " And q: " + - query.toString()); this.response = response; - time_total = DateTime.currentTimeMillis() - query_start; + + query_completed_ts = DateTime.currentTimeMillis(); + overall_stats.put(QueryStat.PROCESSING_PRE_WRITE_TIME, DateTime.nanoTime() - query_start_ns); synchronized (running_queries) { if (!running_queries.containsKey(this.hashCode())) { if (!ENABLE_DUPLICATES) { LOG.warn("Query was already marked as complete: " + this); } - return; } - running_queries.remove(this.hashCode()); - LOG.debug("Removed completed query " + remote_address + " with hash " + - hashCode() + " on thread " + Thread.currentThread().getId()); + running_queries.remove(hashCode()); + if (LOG.isDebugEnabled()) { + LOG.debug("Removed completed query " + remote_address + " with hash " + + hashCode() + " on thread " + Thread.currentThread().getId()); + } } + aggQueryStats(); + final int cache_hash = this.hashCode() ^ response.toString().hashCode(); synchronized (completed_queries) { final QueryStats old_query = completed_queries.getIfPresent(cache_hash); @@ -223,7 +367,25 @@ public void markComplete(final HttpResponseStatus response, old_query.executed++; } } - LOG.info("completed_query=" + JSON.serializeToString(this)); + } + + /** + * Marks the query as complete and logs it to the proper logs. This is called + * after the data has been sent to the client. + */ + public void markSent() { + sent_to_client = true; + overall_stats.put(QueryStat.TOTAL_TIME, DateTime.nanoTime() - query_start_ns); + LOG.info("Completing query=" + JSON.serializeToString(this)); + QUERY_LOG.info(this.toString()); + } + + /** Leaves the sent_to_client field as false when we were unable to write to + * the client end point. */ + public void markSendFailed() { + overall_stats.put(QueryStat.TOTAL_TIME, DateTime.nanoTime() - query_start_ns); + LOG.info("Completing query=" + JSON.serializeToString(this)); + QUERY_LOG.info(this.toString()); } /** @@ -231,15 +393,13 @@ public void markComplete(final HttpResponseStatus response, * returned to a caller. * @return A map for serialization */ - public static Map>> buildStats() { - Map>> root = - new HashMap>>(); + public static Map getRunningAndCompleteStats() { + Map root = new TreeMap(); if (running_queries.isEmpty()) { - root.put("running", Collections.> emptyList()); + root.put("running", Collections.emptyList()); } else { - final List> running = - new ArrayList>(running_queries.size()); + final List running = new ArrayList(running_queries.size()); root.put("running", running); // don't need to lock the map beyond what the iterator will do implicitly @@ -247,38 +407,20 @@ public static Map>> buildStats() { final Map obj = new HashMap(10); obj.put("query", stats.query); obj.put("remote", stats.remote_address); - obj.put("queryStart", stats.query_start); - obj.put("timeTotal", stats.time_total); - obj.put("elapsed", DateTime.currentTimeMillis() - stats.query_start); + obj.put("user", stats.user); + obj.put("headers", stats.headers);; + obj.put("queryStart", DateTime.msFromNano(stats.query_start_ns)); + obj.put("elapsed", DateTime.msFromNanoDiff(DateTime.nanoTime(), + stats.query_start_ns)); running.add(obj); } } final Map completed = completed_queries.asMap(); if (completed.isEmpty()) { - root.put("completed", Collections.> emptyList()); + root.put("completed", Collections.emptyList()); } else { - final List> running = - new ArrayList>(completed.size()); - root.put("completed", running); - - // don't need to lock the map beyond what the iterator will do implicitly - for (final QueryStats stats : completed.values()) { - final Map obj = new HashMap(10); - obj.put("query", stats.query); - obj.put("remote", stats.remote_address); - obj.put("queryStart", stats.query_start); - obj.put("timeTotal", stats.time_total); - obj.put("executed", stats.executed); - obj.put("datapoints", stats.size); - obj.put("rawDatapoints", stats.aggregated_size); - obj.put("status", stats.response.getCode()); - obj.put("timeStorage", stats.time_storage); - obj.put("timeAggregation", stats.time_aggregation); - obj.put("timeSerialization", stats.time_serialization); - obj.put("exception", stats.exception); - running.add(obj); - } + root.put("completed", completed.values()); } return root; @@ -290,92 +432,461 @@ public static Map>> buildStats() { */ public static void collectStats(final StatsCollector collector) { collector.record("query.count", running_queries.size(), "type=running"); + } + + /** + * Add an overall statistic for the query (i.e. not associated with a sub + * query or scanner) + * @param name The name of the stat + * @param value The value to store + */ + public void addStat(final QueryStat name, final long value) { + overall_stats.put(name, value); + } + + /** + * Adds a stat for a sub query, replacing it if it exists. Times must be + * in nanoseconds. + * @param query_index The index of the sub query to update + * @param name The name of the stat to update + * @param value The value to set + */ + public void addStat(final int query_index, final QueryStat name, + final long value) { + Map qs = query_stats.get(query_index); + if (qs == null) { + qs = new HashMap(); + query_stats.put(query_index, qs); + } + qs.put(name, value); + } + + /** + * Aggregates the various stats from the lower to upper levels. This includes + * calculating max and average time values for stats marked as time based. + */ + public void aggQueryStats() { + // These are overall aggregations + final Map> overall_cumulations = + new HashMap>(); - final Map completed = completed_queries.asMap(); - int completed_success = 0; - int completed_error = 0; - for (final QueryStats stats : completed.values()) { - if (stats.response == HttpResponseStatus.OK) { - completed_success += stats.executed; - } else { - completed_error += stats.executed; + // scanner aggs + for (final Entry>> entry : + scanner_stats.entrySet()) { + final int query_index = entry.getKey(); + + final Map> cumulations = + new HashMap>(); + + for (final Entry> scanner : + entry.getValue().entrySet()) { + + for (final Entry stat : scanner.getValue().entrySet()) { + if (stat.getKey().is_time) { + if (!AGG_MAP.containsKey(stat.getKey())) { + // we're not aggregating this value + continue; + } + + // per query aggs + Pair pair = cumulations.get(stat.getKey()); + if (pair == null) { + pair = new Pair(0L, Long.MIN_VALUE); + cumulations.put(stat.getKey(), pair); + } + pair.setKey(pair.getKey() + stat.getValue()); + if (stat.getValue() > pair.getValue()) { + pair.setValue(stat.getValue()); + } + + // overall aggs required here for proper time averaging + pair = overall_cumulations.get(stat.getKey()); + if (pair == null) { + pair = new Pair(0L, Long.MIN_VALUE); + overall_cumulations.put(stat.getKey(), pair); + } + pair.setKey(pair.getKey() + stat.getValue()); + if (stat.getValue() > pair.getValue()) { + pair.setValue(stat.getValue()); + } + } else { + // only add counters for the per query maps as they'll be rolled + // up below into the overall + updateStat(query_index, stat.getKey(), stat.getValue()); + } + } + } + + // per query aggs + for (final Entry> cumulation : + cumulations.entrySet()) { + // names can't be null as we validate above that it exists + final Pair names = AGG_MAP.get(cumulation.getKey()); + addStat(query_index, names.getKey(), + (cumulation.getValue().getKey() / entry.getValue().size())); + addStat(query_index, names.getValue(), cumulation.getValue().getValue()); + } + } + + // handle the per scanner aggs + for (final Entry> cumulation : + overall_cumulations.entrySet()) { + // names can't be null as we validate above that it exists + final Pair names = AGG_MAP.get(cumulation.getKey()); + addStat(names.getKey(), + (cumulation.getValue().getKey() / + (scanner_stats.size() * Const.SALT_BUCKETS()))); + addStat(names.getValue(), cumulation.getValue().getValue()); + } + overall_cumulations.clear(); + + // aggregate counters from the sub queries + for (final Map sub_query : query_stats.values()) { + for (final Entry stat : sub_query.entrySet()) { + if (stat.getKey().is_time) { + if (!AGG_MAP.containsKey(stat.getKey())) { + // we're not aggregating this value + continue; + } + Pair pair = overall_cumulations.get(stat.getKey()); + if (pair == null) { + pair = new Pair(0L, Long.MIN_VALUE); + overall_cumulations.put(stat.getKey(), pair); + } + pair.setKey(pair.getKey() + stat.getValue()); + if (stat.getValue() > pair.getValue()) { + pair.setValue(stat.getValue()); + } + } else if (overall_stats.containsKey(stat.getKey())) { + overall_stats.put(stat.getKey(), + overall_stats.get(stat.getKey()) + stat.getValue()); + } else { + overall_stats.put(stat.getKey(), stat.getValue()); + } } } - collector.record("query.count", completed_success, "type=successful"); - collector.record("query.count", completed_error, "type=failed"); + for (final Entry> cumulation : + overall_cumulations.entrySet()) { + // names can't be null as we validate above that it exists + final Pair names = AGG_MAP.get(cumulation.getKey()); + overall_stats.put(names.getKey(), + (cumulation.getValue().getKey() / query_stats.size())); + overall_stats.put(names.getValue(), cumulation.getValue().getValue()); + } } - /** @return the start time of the query in ms */ - public long getQueryStart() { - return query_start; + /** + * Increments the cumulative value for a cumulative stat. If it's a time then + * it must be in nanoseconds + * @param query_index The index of the sub query + * @param name The name of the stat + * @param value The value to add to the existing value + */ + public void updateStat(final int query_index, final QueryStat name, + final long value) { + Map qs = query_stats.get(query_index); + long cum_time = value; + if (qs == null) { + qs = new HashMap(); + query_stats.put(query_index, qs); + } + + if (qs.containsKey(name)) { + cum_time += qs.get(name); + } + qs.put(name, cum_time); + } + + /** + * Adds a value for a specific scanner for a specific sub query. If it's a time + * then it must be in nanoseconds. + * @param query_index The index of the sub query + * @param id The numeric ID of the scanner + * @param name The name of the stat + * @param value The value to add to the map + */ + public void addScannerStat(final int query_index, final int id, + final QueryStat name, final long value) { + Map> qs = scanner_stats.get(query_index); + if (qs == null) { + qs = new ConcurrentHashMap>(Const.SALT_BUCKETS()); + scanner_stats.put(query_index, qs); + } + Map scanner_stat_map = qs.get(id); + if (scanner_stat_map == null) { + scanner_stat_map = new HashMap(); + qs.put(id, scanner_stat_map); + } + scanner_stat_map.put(name, value); } - /** @return the total number of data points emitted for the query */ - public long getSize() { - return size; + /** + * Adds or overwrites the list of servers scanned by a scanner + * @param query_index The index of the sub query + * @param id The numeric ID of the scanner + * @param servers The list of servers encountered + */ + public void addScannerServers(final int query_index, final int id, + final Set servers) { + Map> query_servers = scanner_servers.get(query_index); + if (query_servers == null) { + query_servers = new ConcurrentHashMap>( + Const.SALT_BUCKETS()); + scanner_servers.put(query_index, query_servers); + } + query_servers.put(id, servers); } - /** @param size increments the number of data points emitted */ - public void addSize(int size) { - this.size += size; + /** + * Updates or adds a stat for a specific scanner. IF it's a time it must + * be in nanoseconds + * @param query_index The index of the sub query + * @param id The numeric ID of the scanner + * @param name The name of the stat + * @param value The value to update to the map + */ + public void updateScannerStat(final int query_index, final int id, + final QueryStat name, final long value) { + Map> qs = scanner_stats.get(query_index); + long cum_time = value; + if (qs == null) { + qs = new ConcurrentHashMap>(); + scanner_stats.put(query_index, qs); + } + Map scanner_stat_map = qs.get(id); + if (scanner_stat_map == null) { + scanner_stat_map = new HashMap(); + qs.put(id, scanner_stat_map); + } + + if (scanner_stat_map.containsKey(name)) { + cum_time += scanner_stat_map.get(name); + } + + scanner_stat_map.put(name, cum_time); + } + + /** + * Adds a scanner for a sub query to the stats along with the description of + * the scanner. + * @param query_index The index of the sub query + * @param id The numeric ID of the scanner + * @param string_id The description of the scanner + */ + public void addScannerId(final int query_index, final int id, + final String string_id) { + Map scanners = scanner_ids.get(query_index); + if (scanners == null) { + scanners = new ConcurrentHashMap(); + scanner_ids.put(query_index, scanners); + } + scanners.put(id, string_id); } - /** @return the total number of data points retrieved from storage */ - public long getAggregatedSize() { - return aggregated_size; + /** @return the start time of the query in nano seconds */ + public long queryStart() { + return query_start_ns; + } + + /** @param user The user who executed the query */ + public void setUser(final String user) { + this.user = user; } - /** @param size increments the number of data points retrieved from storage */ - public void addAggregatedSize(int size) { - aggregated_size += size; + /** @return The user who executed the query if known */ + public String getUser() { + return user; } - /** @param time_storage the amount of time it took to fetch data from storage in ms */ - public void setTimeStorage(final long time_storage) { - this.time_storage = time_storage; + /** @return The multi-mapped set of request headers associated with the query */ + public Map getRequestHeaders() { + return headers; } - /** @return the amount of time it took to fetch data from storage in ms */ - public long getTimeStorage() { - return time_storage; + /** @return The number of currently running queries */ + public int getNumRunningQueries() { + return running_queries.size(); } - /** @param time_aggregation increments the amount of time spent aggregating in ms */ - public void addTimeAggregation(final long time_aggregation) { - this.time_aggregation += time_aggregation; + /** @return An exception associated with the query or null if the query + * returned successfully. */ + public String getException() { + if (exception == null) { + return "null"; + } + return exception.getMessage() + + (exception.getStackTrace() != null && exception.getStackTrace().length > 0 + ? "\n" + exception.getStackTrace()[0].toString() : ""); } - /** @return the mount of time spent aggregating in ms */ - public long getTimeAggregation() { - return time_aggregation; + /** @return The HTTP status response for the query */ + public HttpResponseStatus getHttpResponse() { + return response; } - /** @param time_serialization the amount of time spent serializing in ms */ - public void setTimeSerialization(final long time_serialization) { - this.time_serialization = time_serialization; + /** @return The number of times this query has been executed from the same + * endpoint. */ + public long getExecuted() { + return executed; } - /** @return the mount of time spent serializing in ms */ - public long getTimeSerialization() { - return time_serialization; + /** @return The full query */ + public TSQuery getQuery() { + return query; } - /** @return the amount of time working on the query in ms */ - public long getTimeTotal() { - return time_total; + /** @return When the query was received and started executing, in ms */ + public long getQueryStartTimestamp() { + return query_start_ms; } - - /** @return the Http status code */ - public HttpResponseStatus getStatus() { - return response; + + /** @return When the query was marked as completed, in ms */ + public long getQueryCompletedTimestamp() { + return query_completed_ts; + } + + /** @return Whether or not the data was successfully sent to the client */ + public boolean getSentToClient() { + return sent_to_client; + } + + /** @return A map with the subset of query measurements, not including scanners + * or sub queries */ + public Map getStats() { + return getStats(false, false); } - /** @return an exception if it was associated with this query */ - public Throwable getException() { - return exception; + /** + * Returns measurements of the given query + * @param with_scanners Whether or not to dump individual scanner stats + * @return A map with stats for the query + */ + public Map getStats(final boolean with_sub_queries, + final boolean with_scanners) { + final Map map = new TreeMap(); + + for (final Entry entry : overall_stats.entrySet()) { + if (entry.getKey().is_time) { + map.put(entry.getKey().toString(), DateTime.msFromNano(entry.getValue())); + } else { + map.put(entry.getKey().toString(), entry.getValue()); + } + } + + if (with_sub_queries) { + final Iterator>> it = + query_stats.entrySet().iterator(); + while (it.hasNext()) { + final Entry> entry = it.next(); + final Map qs = new HashMap(1); + qs.put(String.format("queryIdx_%02d", entry.getKey()), + getQueryStats(entry.getKey(), with_scanners)); + map.putAll(qs); + } + } + return map; } + /** + * Returns a map of stats for a single sub query + * @param index The sub query to fetch + * @param with_scanners Whether or not to print detailed stats for each scanner + * @return A map with stats to print or null if the sub query didn't have any + * data. + */ + public Map getQueryStats(final int index, + final boolean with_scanners) { + + final Map qs = query_stats.get(index); + if (qs == null) { + return null; + } + + final Map query_map = new TreeMap(); + query_map.put("queryIndex", index); + + final Iterator> stats_it = + qs.entrySet().iterator(); + while (stats_it.hasNext()) { + final Entry stat = stats_it.next(); + if (stat.getKey().is_time) { + query_map.put(stat.getKey().toString(), + DateTime.msFromNano(stat.getValue())); + } else { + query_map.put(stat.getKey().toString(), stat.getValue()); + } + } + + if (with_scanners) { + final Map> scanner_stats_map = + scanner_stats.get(index); + + final Map scanner_maps = new TreeMap(); + + query_map.put("scannerStats", scanner_maps); + if (scanner_stats_map != null) { + final Map scanners = scanner_ids.get(index); + final Iterator>> scanner_it = + scanner_stats_map.entrySet().iterator(); + while (scanner_it.hasNext()) { + final Entry> scanner = scanner_it.next(); + final Map scanner_map = new TreeMap(); + scanner_maps.put(String.format("scannerIdx_%02d", scanner.getKey()), + scanner_map); + final String id; + if (scanners != null) { + id = scanners.get(scanner.getKey()); + } else { + id = null; + } + scanner_map.put("scannerId", id); + /* Uncomment when AsyncHBase supports this + final Map> servers = scanner_servers.get(index); + if (servers != null) { + scanner_map.put("regionServers", servers.get(scanner.getKey())); + } else { + scanner_map.put("regionServers", null); + } + */ + final Iterator> scanner_stats_it = + scanner.getValue().entrySet().iterator(); + while (scanner_stats_it.hasNext()) { + final Entry scanner_stats = scanner_stats_it.next(); + if (!scanner_stats.getKey().is_time) { + scanner_map.put(scanner_stats.getKey().toString(), + scanner_stats.getValue()); + } else { + scanner_map.put(scanner_stats.getKey().toString(), + DateTime.msFromNano(scanner_stats.getValue())); + } + } + } + } + } + return query_map; + } + + /** @return A stat for the overall query or -1 if the stat didn't exist. */ + public long getStat(final QueryStat stat) { + if (!overall_stats.containsKey(stat)) { + return -1; + } + return overall_stats.get(stat); + } + + /** @return a timed stat for the overall query or NaN if the stat didn't exist */ + public double getTimeStat(final QueryStat stat) { + if (!stat.is_time) { + throw new IllegalArgumentException("The stat is not a time stat"); + } + if (!overall_stats.containsKey(stat)) { + return Double.NaN; + } + return DateTime.msFromNano(overall_stats.get(stat)); + } + /** @param whether or not to allow duplicate queries to run */ public static void setEnableDuplicates(final boolean enable_dupes) { ENABLE_DUPLICATES = enable_dupes; diff --git a/src/tsd/AbstractHttpQuery.java b/src/tsd/AbstractHttpQuery.java index ba6b736124..967eaab09f 100644 --- a/src/tsd/AbstractHttpQuery.java +++ b/src/tsd/AbstractHttpQuery.java @@ -14,8 +14,10 @@ import java.nio.charset.Charset; import java.nio.charset.UnsupportedCharsetException; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import com.google.common.base.Objects; import com.stumbleupon.async.Deferred; @@ -35,6 +37,7 @@ import org.slf4j.LoggerFactory; import net.opentsdb.core.TSDB; +import net.opentsdb.stats.QueryStats; /** * Abstract base class for HTTP queries. @@ -70,6 +73,9 @@ public abstract class AbstractHttpQuery { /** The {@code TSDB} instance we belong to */ protected final TSDB tsdb; + /** Used for recording query statistics */ + protected QueryStats stats; + /** * Set up required internal state. For subclasses. * @@ -112,6 +118,58 @@ public String getRemoteAddress() { return chan.getRemoteAddress().toString(); } + /** + * Copies the header list and obfuscates the "cookie" header in case it + * contains auth tokens, etc. Note that it flattens duplicate headers keys + * as comma separated lists per the RFC + * @return The full set of headers for this query with the cookie obfuscated + */ + public Map getPrintableHeaders() { + final Map headers = new HashMap( + request.getHeaders().size()); + for (final Entry header : request.getHeaders()) { + if (header.getKey().toLowerCase().equals("cookie")) { + // null out the cookies + headers.put(header.getKey(), "*******"); + } else { + // http://tools.ietf.org/html/rfc2616#section-4.2 + if (headers.containsKey(header.getKey())) { + headers.put(header.getKey(), + headers.get(header.getKey()) + "," + header.getValue()); + } else { + headers.put(header.getKey(), header.getValue()); + } + } + } + return headers; + } + + /** + * Copies the header list so modifications won't affect the original set. + * Note that it flattens duplicate headers keys as comma separated lists + * per the RFC + * @return The full set of headers for this query + */ + public Map getHeaders() { + final Map headers = new HashMap( + request.getHeaders().size()); + for (final Entry header : request.getHeaders()) { + // http://tools.ietf.org/html/rfc2616#section-4.2 + if (headers.containsKey(header.getKey())) { + headers.put(header.getKey(), + headers.get(header.getKey()) + "," + header.getValue()); + } else { + headers.put(header.getKey(), header.getValue()); + } + } + return headers; + } + + /** @param stats The stats object to mark after writing is complete */ + public void setStats(final QueryStats stats) { + this.stats = stats; + } + /** Return the time in nanoseconds that this query object was * created. */ @@ -192,7 +250,6 @@ public boolean hasQueryStringParam(final String paramname) { public List getQueryStringParams(final String paramname) { return getQueryString().get(paramname); } - /** * Returns only the path component of the URI as a string @@ -339,6 +396,9 @@ public void sendStatusOnly(final HttpResponseStatus status) { HttpHeaders.setContentLength(response, 0); } final ChannelFuture future = chan.write(response); + if (stats != null) { + future.addListener(new SendSuccess()); + } if (!keepalive) { future.addListener(ChannelFutureListener.CLOSE); } @@ -369,12 +429,23 @@ public void sendBuffer(final HttpResponseStatus status, HttpHeaders.setContentLength(response, buf.readableBytes()); } final ChannelFuture future = chan.write(response); + if (stats != null) { + future.addListener(new SendSuccess()); + } if (!keepalive) { future.addListener(ChannelFutureListener.CLOSE); } done(); } + /** A simple class that marks a query as complete when the stats are set */ + private class SendSuccess implements ChannelFutureListener { + @Override + public void operationComplete(final ChannelFuture future) throws Exception { + stats.markSent(); + } + } + /** @return Information about the query */ public String toString() { return Objects.toStringHelper(this) diff --git a/src/tsd/HttpJsonSerializer.java b/src/tsd/HttpJsonSerializer.java index 57fcf1b567..e57acf208a 100644 --- a/src/tsd/HttpJsonSerializer.java +++ b/src/tsd/HttpJsonSerializer.java @@ -45,6 +45,7 @@ import net.opentsdb.meta.UIDMeta; import net.opentsdb.search.SearchQuery; import net.opentsdb.stats.QueryStats; +import net.opentsdb.stats.QueryStats.QueryStat; import net.opentsdb.tree.Branch; import net.opentsdb.tree.Tree; import net.opentsdb.tree.TreeRule; @@ -604,6 +605,8 @@ class DPsResolver implements Callback, Object> { new ArrayList>(); /** The data points to serialize */ final DataPoints dps; + /** Starting time in nanos when we sent the UID resolution queries off */ + long uid_start; public DPsResolver(final DataPoints dps) { this.dps = dps; @@ -653,6 +656,9 @@ public WriteToBuffer(final DataPoints dps) { * variables. */ public Object call(final ArrayList deferreds) throws Exception { + data_query.getQueryStats().addStat(dps.getQueryIndex(), + QueryStat.UID_TO_STRING_TIME, (DateTime.nanoTime() - uid_start)); + final long local_serialization_start = DateTime.nanoTime(); final TSSubQuery orig_query = data_query.getQueries() .get(dps.getQueryIndex()); @@ -715,8 +721,9 @@ public Object call(final ArrayList deferreds) throws Exception { // now the fun stuff, dump the data and time just the iteration over // the data points - final long dps_start = DateTime.currentTimeMillis(); + final long dps_start = DateTime.nanoTime(); json.writeFieldName("dps"); + long counter = 0; // default is to write a map, otherwise write arrays if (!timeout_flag.get(0) && as_arrays) { @@ -743,6 +750,7 @@ public Object call(final ArrayList deferreds) throws Exception { } } json.writeEndArray(); + ++counter; } json.writeEndArray(); } else if (!timeout_flag.get(0)) { @@ -766,28 +774,39 @@ public Object call(final ArrayList deferreds) throws Exception { json.writeNumberField(Long.toString(timestamp), dp.doubleValue()); } } + ++counter; } json.writeEndObject(); + } else { // skipping data points all together due to timeout json.writeStartObject(); json.writeEndObject(); } - - final long agg_time = DateTime.currentTimeMillis() - dps_start; - data_query.getQueryStats().addTimeAggregation(agg_time); - data_query.getQueryStats().addAggregatedSize(dps.aggregatedSize()); - data_query.getQueryStats().addSize(dps.size()); + final long agg_time = DateTime.nanoTime() - dps_start; + data_query.getQueryStats().addStat(dps.getQueryIndex(), + QueryStat.AGGREGATION_TIME, agg_time); + data_query.getQueryStats().addStat(dps.getQueryIndex(), + QueryStat.AGGREGATED_SIZE, counter); + + // yeah, it's a little early but we need to dump it out with the results. + data_query.getQueryStats().addStat(dps.getQueryIndex(), + QueryStat.SERIALIZATION_TIME, + DateTime.nanoTime() - local_serialization_start); if (!timeout_flag.get(0) && data_query.getShowStats()) { - json.writeFieldName("stats"); - json.writeStartObject(); - json.writeNumberField("datapoints", dps.size()); - json.writeNumberField("rawDatapoints", dps.aggregatedSize()); - json.writeNumberField("aggregationTime", agg_time); - json.writeNumberField("timeSeries", dps.getTSUIDs().size()); - // todo - timing for just this query - json.writeEndObject(); + int query_index = (dps == null) ? -1 : dps.getQueryIndex(); + QueryStats stats = data_query.getQueryStats(); + + if (query_index >= 0) { + json.writeFieldName("stats"); + final Map s = stats.getQueryStats(query_index, false); + if (s != null) { + json.writeObject(s); + } else { + json.writeStringField("ERROR", "NO STATS FOUND"); + } + } } // close the results for this particular query @@ -801,6 +820,8 @@ public Object call(final ArrayList deferreds) throws Exception { * then prints to the output buffer once they are completed. */ public Deferred call(final Object obj) throws Exception { + this.uid_start = DateTime.nanoTime(); + resolve_deferreds.add(dps.metricNameAsync() .addCallback(new MetricResolver())); resolve_deferreds.add(dps.getTagsAsync() @@ -832,23 +853,19 @@ public Deferred call(final Object obj) throws Exception { class FinalCB implements Callback { public ChannelBuffer call(final Object obj) throws Exception { - data_query.getQueryStats().setTimeSerialization( - DateTime.currentTimeMillis() - start); - data_query.getQueryStats().markComplete(); + + // Call this here so we rollup sub metrics into a summary. It's not + // completely accurate, of course, because we still have to write the + // summary and close the writer. But it's close. + data_query.getQueryStats().markSerializationSuccessful(); // dump overall stats as an extra object in the array + // TODO - yeah, I've heard this sucks, we need to figure out a better way. if (data_query.getShowSummary()) { final QueryStats stats = data_query.getQueryStats(); json.writeStartObject(); json.writeFieldName("statsSummary"); - json.writeStartObject(); - json.writeNumberField("datapoints", stats.getSize()); - json.writeNumberField("rawDatapoints", stats.getAggregatedSize()); - json.writeNumberField("aggregationTime", stats.getTimeAggregation()); - json.writeNumberField("serializationTime", stats.getTimeSerialization()); - json.writeNumberField("storageTime", stats.getTimeStorage()); - json.writeNumberField("timeTotal", stats.getTimeTotal()); - json.writeEndObject(); + json.writeObject(stats.getStats(true, true)); json.writeEndObject(); } @@ -1060,8 +1077,7 @@ public ChannelBuffer formatJVMStatsV1(final Map> sta * @throws BadRequestException if the plugin has not implemented this method * @since 2.2 */ - public ChannelBuffer formatQueryStatsV1( - final Map>> query_stats) { + public ChannelBuffer formatQueryStatsV1(final Map query_stats) { return serializeJSON(query_stats); } diff --git a/src/tsd/HttpSerializer.java b/src/tsd/HttpSerializer.java index 8f16ea9590..34adc3e01d 100644 --- a/src/tsd/HttpSerializer.java +++ b/src/tsd/HttpSerializer.java @@ -720,8 +720,7 @@ public ChannelBuffer formatJVMStatsV1(final Map> map * @throws BadRequestException if the plugin has not implemented this method * @since 2.2 */ - public ChannelBuffer formatQueryStatsV1( - final Map>> query_stats) { + public ChannelBuffer formatQueryStatsV1(final Map query_stats) { throw new BadRequestException(HttpResponseStatus.NOT_IMPLEMENTED, "The requested API endpoint has not been implemented", this.getClass().getCanonicalName() + diff --git a/src/tsd/QueryRpc.java b/src/tsd/QueryRpc.java index bef1a64cf7..a9e87566c2 100644 --- a/src/tsd/QueryRpc.java +++ b/src/tsd/QueryRpc.java @@ -19,7 +19,10 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; +import org.hbase.async.HBaseException; +import org.hbase.async.RpcTimedOutException; import org.hbase.async.Bytes.ByteMap; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.handler.codec.http.HttpMethod; @@ -46,6 +49,7 @@ import net.opentsdb.query.expression.Expressions; import net.opentsdb.query.filter.TagVFilter; import net.opentsdb.stats.QueryStats; +import net.opentsdb.stats.StatsCollector; import net.opentsdb.uid.NoSuchUniqueName; import net.opentsdb.uid.UniqueId; import net.opentsdb.utils.DateTime; @@ -65,6 +69,11 @@ final class QueryRpc implements HttpRpc { private static final Logger LOG = LoggerFactory.getLogger(QueryRpc.class); + /** Various counters and metrics for reporting query stats */ + static final AtomicLong query_invalid = new AtomicLong(); + static final AtomicLong query_exceptions = new AtomicLong(); + static final AtomicLong query_success = new AtomicLong(); + /** * Implements the /api/query endpoint to fetch data from OpenTSDB. * @param tsdb The TSDB to use for fetching data @@ -121,7 +130,8 @@ private void handleQuery(final TSDB tsdb, final HttpQuery query, case 1: data_query = query.serializer().parseQueryV1(); break; - default: + default: + query_invalid.incrementAndGet(); throw new BadRequestException(HttpResponseStatus.NOT_IMPLEMENTED, "Requested API version not implemented", "Version " + query.apiVersion() + " is not implemented"); @@ -149,8 +159,10 @@ private void handleQuery(final TSDB tsdb, final HttpQuery query, // if the user tried this query multiple times from the same IP and src port // they'll be rejected on subsequent calls final QueryStats query_stats = - new QueryStats(query.getRemoteAddress(), data_query); + new QueryStats(query.getRemoteAddress(), data_query, + query.getPrintableHeaders()); data_query.setQueryStats(query_stats); + query.setStats(query_stats); final int nqueries = data_query.getQueries().size(); final ArrayList results = new ArrayList(nqueries); @@ -159,43 +171,56 @@ private void handleQuery(final TSDB tsdb, final HttpQuery query, /** This has to be attached to callbacks or we may never respond to clients */ class ErrorCB implements Callback { public Object call(final Exception e) throws Exception { + Throwable ex = e; try { - if (e instanceof DeferredGroupException) { - Throwable ex = e.getCause(); + LOG.error("Query exception: ", e); + if (ex instanceof DeferredGroupException) { + ex = e.getCause(); while (ex != null && ex instanceof DeferredGroupException) { ex = ex.getCause(); } - if (ex != null) { - if (ex instanceof NoSuchUniqueName) { - query_stats.markComplete(HttpResponseStatus.BAD_REQUEST, ex); - query.badRequest(new BadRequestException( - HttpResponseStatus.NOT_FOUND, ex.getMessage())); - return null; - } - LOG.error("Query failed", ex); - query_stats.markComplete(HttpResponseStatus.INTERNAL_SERVER_ERROR, ex); - query.badRequest(new BadRequestException(ex)); - } else { - LOG.error("Unable to find the cause of the DGE", e); - query_stats.markComplete(HttpResponseStatus.INTERNAL_SERVER_ERROR, e); - query.badRequest(new BadRequestException(e)); + if (ex == null) { + LOG.error("The deferred group exception didn't have a cause???"); } - } else if (e.getClass() == QueryException.class) { - query_stats.markComplete(HttpResponseStatus.REQUEST_TIMEOUT, e); - query.badRequest(new BadRequestException((QueryException)e)); + } + + if (ex instanceof RpcTimedOutException) { + query_stats.markSerialized(HttpResponseStatus.REQUEST_TIMEOUT, ex); + query.badRequest(new BadRequestException( + HttpResponseStatus.REQUEST_TIMEOUT, ex.getMessage())); + query_exceptions.incrementAndGet(); + } else if (ex instanceof HBaseException) { + query_stats.markSerialized(HttpResponseStatus.FAILED_DEPENDENCY, ex); + query.badRequest(new BadRequestException( + HttpResponseStatus.FAILED_DEPENDENCY, ex.getMessage())); + query_exceptions.incrementAndGet(); + } else if (ex instanceof QueryException) { + query_stats.markSerialized(((QueryException)ex).getStatus(), ex); + query.badRequest(new BadRequestException( + ((QueryException)ex).getStatus(), ex.getMessage())); + query_exceptions.incrementAndGet(); + } else if (ex instanceof BadRequestException) { + query_stats.markSerialized(((BadRequestException)ex).getStatus(), ex); + query.badRequest((BadRequestException)ex); + query_invalid.incrementAndGet(); + } else if (ex instanceof NoSuchUniqueName) { + query_stats.markSerialized(HttpResponseStatus.BAD_REQUEST, ex); + query.badRequest(new BadRequestException(ex)); + query_invalid.incrementAndGet(); } else { - LOG.error("Query failed", e); - query_stats.markComplete(HttpResponseStatus.INTERNAL_SERVER_ERROR, e); - query.badRequest(new BadRequestException(e)); + query_stats.markSerialized(HttpResponseStatus.INTERNAL_SERVER_ERROR, ex); + query.badRequest(new BadRequestException(ex)); + query_exceptions.incrementAndGet(); } - return null; - } catch (RuntimeException ex) { - LOG.error("Exception thrown during exception handling", ex); - query_stats.markComplete(HttpResponseStatus.INTERNAL_SERVER_ERROR, e); + + } catch (RuntimeException ex2) { + LOG.error("Exception thrown during exception handling", ex2); + query_stats.markSerialized(HttpResponseStatus.INTERNAL_SERVER_ERROR, ex2); query.sendReply(HttpResponseStatus.INTERNAL_SERVER_ERROR, - ex.getMessage().getBytes()); - return null; + ex2.getMessage().getBytes()); + query_exceptions.incrementAndGet(); } + return null; } } @@ -224,11 +249,11 @@ public Object call(final ArrayList query_results) class SendIt implements Callback { public Object call(final ChannelBuffer buffer) throws Exception { query.sendReply(buffer); + query_success.incrementAndGet(); return null; } } - query_stats.setTimeStorage(System.currentTimeMillis() - start); switch (query.apiVersion()) { case 0: case 1: @@ -236,6 +261,7 @@ public Object call(final ChannelBuffer buffer) throws Exception { globals).addCallback(new SendIt()).addErrback(new ErrorCB()); break; default: + query_invalid.incrementAndGet(); throw new BadRequestException(HttpResponseStatus.NOT_IMPLEMENTED, "Requested API version not implemented", "Version " + query.apiVersion() + " is not implemented"); @@ -768,6 +794,13 @@ private LastPointQuery parseLastPointQuery(final TSDB tsdb, return query; } + /** @param collector Populates the collector with statistics */ + public static void collectStats(final StatsCollector collector) { + collector.record("http.query.invalid_requests", query_invalid); + collector.record("http.query.exceptions", query_exceptions); + collector.record("http.query.success", query_success); + } + public static class LastPointQuery { private boolean resolve_names; diff --git a/src/tsd/RpcHandler.java b/src/tsd/RpcHandler.java index e6bf4b588c..69224ca8b5 100644 --- a/src/tsd/RpcHandler.java +++ b/src/tsd/RpcHandler.java @@ -331,6 +331,7 @@ public static void collectStats(final StatsCollector collector) { HttpQuery.collectStats(collector); GraphHandler.collectStats(collector); PutDataPointRpc.collectStats(collector); + QueryRpc.collectStats(collector); } /** diff --git a/src/tsd/StatsRpc.java b/src/tsd/StatsRpc.java index 0e65d17a47..bd0ee910d1 100644 --- a/src/tsd/StatsRpc.java +++ b/src/tsd/StatsRpc.java @@ -331,7 +331,7 @@ private void printQueryStats(final HttpQuery query) { case 0: case 1: query.sendReply(query.serializer().formatQueryStatsV1( - QueryStats.buildStats())); + QueryStats.getRunningAndCompleteStats())); break; default: throw new BadRequestException(HttpResponseStatus.NOT_IMPLEMENTED, diff --git a/src/utils/DateTime.java b/src/utils/DateTime.java index 97b052c0bb..d3d9a08c44 100644 --- a/src/utils/DateTime.java +++ b/src/utils/DateTime.java @@ -268,4 +268,41 @@ public static long currentTimeMillis() { return System.currentTimeMillis(); } + /** + * Pass through to {@link System.nanoTime} for use in classes to + * make unit testing easier. Mocking System.class is a bad idea in general + * so placing this here and mocking DateTime.class is MUCH cleaner. + * @return The current epoch time in milliseconds + * @since 2.2 + */ + public static long nanoTime() { + return System.nanoTime(); + } + + /** + * Converts the long nanosecond value to a double in milliseconds + * @param ts The timestamp or value in nanoseconds + * @return The timestamp in milliseconds + * @since 2.2 + */ + public static double msFromNano(final long ts) { + return (double)ts / 1000000; + } + + /** + * Calculates the difference between two values and returns the time in + * milliseconds as a double. + * @param end The end timestamp + * @param start The start timestamp + * @return The value in milliseconds + * @throws IllegalArgumentException if end is less than start + * @since 2.2 + */ + public static double msFromNanoDiff(final long end, final long start) { + if (end < start) { + throw new IllegalArgumentException("End (" + end + ") cannot be less " + + "than start (" + start + ")"); + } + return ((double) end - (double) start) / 1000000; + } } diff --git a/test/stats/TestQueryStats.java b/test/stats/TestQueryStats.java index 5ee4dcf163..e81477d15d 100644 --- a/test/stats/TestQueryStats.java +++ b/test/stats/TestQueryStats.java @@ -15,9 +15,14 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.anyLong; import java.lang.reflect.Field; +import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -26,14 +31,21 @@ import net.opentsdb.core.QueryException; import net.opentsdb.core.TSQuery; +import net.opentsdb.stats.QueryStats.QueryStat; +import net.opentsdb.utils.DateTime; import org.jboss.netty.handler.codec.http.HttpResponseStatus; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; @RunWith(PowerMockRunner.class) +@PrepareForTest({ DateTime.class, QueryStats.class }) public final class TestQueryStats { private static String remote = "192.168.1.1:4242"; @@ -56,65 +68,100 @@ public final class TestQueryStats { } } + private Map headers; + @Before public void before() throws Exception { running_queries.set(null, new ConcurrentHashMap()); completed_queries.set(null, CacheBuilder.newBuilder().maximumSize(2).build()); + headers = new HashMap(1); + headers.put("Cookie", "Hide me!"); + PowerMockito.mockStatic(DateTime.class); + PowerMockito.doAnswer(new Answer() { + long ts = 1000L; + @Override + public Long answer(InvocationOnMock invocation) throws Throwable { + return ts += 1000000000L; + } + + }).when(DateTime.class, "nanoTime"); + PowerMockito.doCallRealMethod().when(DateTime.class, + "msFromNano", anyLong()); + PowerMockito.doCallRealMethod().when(DateTime.class, + "msFromNanoDiff", anyLong(), anyLong()); } @Test public void ctor() throws Exception { final TSQuery query = new TSQuery(); query.setStart("1h-ago"); - final QueryStats stats = new QueryStats(remote, query); + final QueryStats stats = new QueryStats(remote, query, headers); assertNotNull(stats); - final Map>> map = QueryStats.buildStats(); + final Map map = QueryStats.getRunningAndCompleteStats(); assertNotNull(map); - assertEquals(1, map.get("running").size()); - assertEquals(0, map.get("completed").size()); + assertEquals(1, ((List)map.get("running")).size()); + assertEquals(0, ((Collection)map.get("completed")).size()); + assertSame(headers, stats.getRequestHeaders()); } - @Test (expected = QueryException.class) + @Test public void ctorDuplicate() throws Exception { + QueryStats.setEnableDuplicates(false); final TSQuery query = new TSQuery(); query.setStart("1h-ago"); - final QueryStats stats = new QueryStats(remote, query); + final QueryStats stats = new QueryStats(remote, query, headers); assertNotNull(stats); - final Map>> map = QueryStats.buildStats(); + final Map map = QueryStats.getRunningAndCompleteStats(); assertNotNull(map); - assertEquals(1, map.get("running").size()); - assertEquals(0, map.get("completed").size()); - new QueryStats(remote, query); + assertEquals(1, ((List)map.get("running")).size()); + assertEquals(0, ((Collection)map.get("completed")).size()); + try { + new QueryStats(remote, query, headers); + fail("Expected a QueryException"); + } catch (QueryException e) { } + QueryStats.setEnableDuplicates(true); } @Test (expected = IllegalArgumentException.class) public void ctorNullRemote() throws Exception { final TSQuery query = new TSQuery(); query.setStart("1h-ago"); - new QueryStats(null, query); + new QueryStats(null, query, headers); } @Test (expected = IllegalArgumentException.class) public void ctorNullQuery() throws Exception { final TSQuery query = new TSQuery(); query.setStart("1h-ago"); - new QueryStats(remote, null); + new QueryStats(remote, null, headers); + } + + @Test + public void ctorNullHeaders() throws Exception { + final TSQuery query = new TSQuery(); + query.setStart("1h-ago"); + final QueryStats stats = new QueryStats(remote, query, null); + assertNotNull(stats); + final Map map = QueryStats.getRunningAndCompleteStats(); + assertNotNull(map); + assertEquals(1, ((List)map.get("running")).size()); + assertEquals(0, ((Collection)map.get("completed")).size()); } @Test public void testHashCodeandEquals() throws Exception { final TSQuery query = new TSQuery(); query.setStart("1h-ago"); - final QueryStats stats = new QueryStats(remote, query); + final QueryStats stats = new QueryStats(remote, query, headers); assertNotNull(stats); final int hash_a = stats.hashCode(); // have to mark the old one as complete before we can test equality - stats.markComplete(); + stats.markSerializationSuccessful(); final TSQuery query2 = new TSQuery(); query2.setStart("1h-ago"); - final QueryStats stats2 = new QueryStats(remote, query2); + final QueryStats stats2 = new QueryStats(remote, query2, headers); assertNotNull(stats); assertEquals(hash_a, stats2.hashCode()); assertEquals(stats, stats2); @@ -125,13 +172,13 @@ public void testHashCodeandEquals() throws Exception { public void testHashCodeandNotEquals() throws Exception { final TSQuery query = new TSQuery(); query.setStart("1h-ago"); - final QueryStats stats = new QueryStats(remote, query); + final QueryStats stats = new QueryStats(remote, query, headers); assertNotNull(stats); final int hash_a = stats.hashCode(); final TSQuery query2 = new TSQuery(); query2.setStart("2h-ago"); - final QueryStats stats2 = new QueryStats(remote, query2); + final QueryStats stats2 = new QueryStats(remote, query2, headers); assertNotNull(stats); assertTrue(hash_a != stats2.hashCode()); assertFalse(stats.equals(stats2)); @@ -142,7 +189,7 @@ public void testHashCodeandNotEquals() throws Exception { public void testEqualsNull() throws Exception { final TSQuery query = new TSQuery(); query.setStart("1h-ago"); - final QueryStats stats = new QueryStats(remote, query); + final QueryStats stats = new QueryStats(remote, query, headers); assertFalse(stats.equals(null)); } @@ -150,7 +197,7 @@ public void testEqualsNull() throws Exception { public void testEqualsWrongType() throws Exception { final TSQuery query = new TSQuery(); query.setStart("1h-ago"); - final QueryStats stats = new QueryStats(remote, query); + final QueryStats stats = new QueryStats(remote, query, headers); assertFalse(stats.equals(new String("foo"))); } @@ -158,7 +205,7 @@ public void testEqualsWrongType() throws Exception { public void testEqualsSame() throws Exception { final TSQuery query = new TSQuery(); query.setStart("1h-ago"); - final QueryStats stats = new QueryStats(remote, query); + final QueryStats stats = new QueryStats(remote, query, headers); assertTrue(stats.equals(stats)); } @@ -166,84 +213,92 @@ public void testEqualsSame() throws Exception { public void markComplete() throws Exception { final TSQuery query = new TSQuery(); query.setStart("1h-ago"); - final QueryStats stats = new QueryStats(remote, query); - stats.markComplete(); - final Map>> map = QueryStats.buildStats(); + final QueryStats stats = new QueryStats(remote, query, headers); + stats.markSerializationSuccessful(); + final Map map = QueryStats.getRunningAndCompleteStats(); assertNotNull(map); - assertEquals(0, map.get("running").size()); - assertEquals(1, map.get("completed").size()); - final Map completed = map.get("completed").get(0); - assertEquals(200, completed.get("status")); + assertEquals(0, ((List)map.get("running")).size()); + assertEquals(1, ((Collection)map.get("completed")).size()); + final QueryStats completed = ((Collection)map.get("completed")) + .iterator().next(); + assertEquals(200, completed.getHttpResponse().getCode()); } @Test public void markCompleteTimeout() throws Exception { final TSQuery query = new TSQuery(); query.setStart("1h-ago"); - final QueryStats stats = new QueryStats(remote, query); - stats.markComplete(HttpResponseStatus.REQUEST_TIMEOUT, null); - final Map>> map = QueryStats.buildStats(); + final QueryStats stats = new QueryStats(remote, query, headers); + final RuntimeException timeout = new RuntimeException("Timeout!"); + stats.markSerialized(HttpResponseStatus.REQUEST_TIMEOUT, timeout); + final Map map = QueryStats.getRunningAndCompleteStats(); assertNotNull(map); - assertEquals(0, map.get("running").size()); - assertEquals(1, map.get("completed").size()); - final Map completed = map.get("completed").get(0); - assertEquals(408, completed.get("status")); + assertEquals(0, ((List)map.get("running")).size()); + assertEquals(1, ((Collection)map.get("completed")).size()); + final QueryStats completed = ((Collection)map.get("completed")) + .iterator().next(); + assertEquals(408, completed.getHttpResponse().getCode()); + assertTrue(completed.getException().startsWith("Timeout!\n")); } @Test - public void markCompleteDoubleMark() throws Exception { + public void executed() throws Exception { final TSQuery query = new TSQuery(); query.setStart("1h-ago"); - final QueryStats stats = new QueryStats(remote, query); - stats.markComplete(); - final Map>> map = QueryStats.buildStats(); - assertNotNull(map); - assertEquals(0, map.get("running").size()); - assertEquals(1, map.get("completed").size()); - Map completed = map.get("completed").get(0); - assertEquals(200, completed.get("status")); - stats.markComplete(); + final QueryStats stats = new QueryStats(remote, query, headers); + stats.markSerialized(HttpResponseStatus.REQUEST_TIMEOUT, null); + final Map map = QueryStats.getRunningAndCompleteStats(); assertNotNull(map); - assertEquals(0, map.get("running").size()); - assertEquals(1, map.get("completed").size()); - completed = map.get("completed").get(0); - assertEquals(200, completed.get("status")); + assertEquals(0, ((List)map.get("running")).size()); + assertEquals(1, ((Collection)map.get("completed")).size()); + final QueryStats completed = ((Collection)map.get("completed")) + .iterator().next(); + assertEquals(1, completed.getExecuted()); } @Test - public void executed() throws Exception { + public void executedTwice() throws Exception { final TSQuery query = new TSQuery(); query.setStart("1h-ago"); - final QueryStats stats = new QueryStats(remote, query); - stats.markComplete(HttpResponseStatus.REQUEST_TIMEOUT, null); - final Map>> map = QueryStats.buildStats(); + QueryStats stats = new QueryStats(remote, query, headers); + stats.markSerialized(HttpResponseStatus.REQUEST_TIMEOUT, null); + Map map = QueryStats.getRunningAndCompleteStats(); + assertNotNull(map); + assertEquals(0, ((List)map.get("running")).size()); + assertEquals(1, ((Collection)map.get("completed")).size()); + QueryStats completed = ((Collection)map.get("completed")) + .iterator().next(); + assertEquals(1, completed.getExecuted()); + + stats = new QueryStats(remote, query, headers); + stats.markSerialized(HttpResponseStatus.REQUEST_TIMEOUT, null); + map = QueryStats.getRunningAndCompleteStats(); assertNotNull(map); - assertEquals(0, map.get("running").size()); - assertEquals(1, map.get("completed").size()); - final Map completed = map.get("completed").get(0); - assertEquals(1L, completed.get("executed")); + assertEquals(0, ((List)map.get("running")).size()); + assertEquals(1, ((Collection)map.get("completed")).size()); + completed = ((Collection)map.get("completed")) + .iterator().next(); + assertEquals(2, completed.getExecuted()); + } + + @Test + public void getStat() throws Exception { + final TSQuery query = new TSQuery(); + query.setStart("1h-ago"); + final QueryStats stats = new QueryStats(remote, query, headers); + stats.addStat(QueryStat.AGGREGATED_SIZE, 42); + stats.markSerializationSuccessful(); + assertEquals(42, stats.getStat(QueryStat.AGGREGATED_SIZE)); + assertEquals(-1, stats.getStat(QueryStat.BYTES_FROM_STORAGE)); } @Test - public void executedTwice() throws Exception { + public void getStatTime() throws Exception { final TSQuery query = new TSQuery(); query.setStart("1h-ago"); - QueryStats stats = new QueryStats(remote, query); - stats.markComplete(HttpResponseStatus.REQUEST_TIMEOUT, null); - Map>> map = QueryStats.buildStats(); - assertNotNull(map); - assertEquals(0, map.get("running").size()); - assertEquals(1, map.get("completed").size()); - Map completed = map.get("completed").get(0); - assertEquals(1L, completed.get("executed")); - - stats = new QueryStats(remote, query); - stats.markComplete(HttpResponseStatus.REQUEST_TIMEOUT, null); - map = QueryStats.buildStats(); - assertNotNull(map); - assertEquals(0, map.get("running").size()); - assertEquals(1, map.get("completed").size()); - completed = map.get("completed").get(0); - assertEquals(2L, completed.get("executed")); + final QueryStats stats = new QueryStats(remote, query, headers); + stats.markSerializationSuccessful(); + assertEquals(1000.0, stats.getTimeStat(QueryStat.PROCESSING_PRE_WRITE_TIME), 0.001); + assertEquals(Double.NaN, stats.getTimeStat(QueryStat.AVG_AGGREGATION_TIME), 0.001); } } diff --git a/test/tsd/NettyMocks.java b/test/tsd/NettyMocks.java index 7937f97bbf..f641ea9550 100644 --- a/test/tsd/NettyMocks.java +++ b/test/tsd/NettyMocks.java @@ -12,6 +12,7 @@ // see . package net.opentsdb.tsd; +import static org.mockito.Matchers.any; import static org.mockito.Mockito.when; import static org.powermock.api.mockito.PowerMockito.mock; @@ -22,8 +23,11 @@ import net.opentsdb.core.TSDB; import net.opentsdb.utils.Config; +import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.DefaultChannelFuture; import org.jboss.netty.channel.DefaultChannelPipeline; import org.jboss.netty.handler.codec.http.DefaultHttpRequest; import org.jboss.netty.handler.codec.http.HttpMethod; @@ -198,6 +202,13 @@ public static HttpQuery contentQuery(final TSDB tsdb, final String uri, req.headers().set("Content-Type", type); return new HttpQuery(tsdb, req, channelMock); } + + /** @param the query to mock a future callback for */ + public static void mockChannelFuture(final HttpQuery query) { + final ChannelFuture future = new DefaultChannelFuture(query.channel(), false); + when(query.channel().write(any(ChannelBuffer.class))).thenReturn(future); + future.setSuccess(); + } /** * Returns a simple pipeline with an HttpRequestDecoder and an diff --git a/test/tsd/TestHttpJsonSerializer.java b/test/tsd/TestHttpJsonSerializer.java index a4669a87b1..1c746f55a2 100644 --- a/test/tsd/TestHttpJsonSerializer.java +++ b/test/tsd/TestHttpJsonSerializer.java @@ -266,18 +266,15 @@ public void formatQueryAsyncV1wStatsSummary() throws Exception { assertTrue(json.contains("\"metric\":\"system.cpu.user\",")); assertTrue(json.contains("\"1356998700\":1,")); assertTrue(json.contains("\"1357058700\":201")); - //assert stats assertTrue(json.contains("\"stats\":{")); - assertTrue(json.contains("\"datapoints\":400")); - assertTrue(json.contains("\"rawDatapoints\":800")); - assertTrue(json.contains("\"timeSeries\":2")); - + assertTrue(json.contains("\"emittedDPs\":401")); + System.out.println(json); //assert stats summary assertTrue(json.contains("{\"statsSummary\":{")); - assertTrue(json.contains("\"serializationTime\":1500")); - assertTrue(json.contains("\"storageTime\":0")); - assertTrue(json.contains("\"timeTotal\":2500")); + assertTrue(json.contains("\"serializationTime\":")); + assertTrue(json.contains("\"processingPreWriteTime\":")); + assertTrue(json.contains("\"queryIdx_00\":")); } @Test @@ -299,12 +296,9 @@ public void formatQueryAsyncV1wStatsWoSummary() throws Exception { assertTrue(json.contains("\"1356998700\":1,")); assertTrue(json.contains("\"1357058700\":201")); - //assert stats assertTrue(json.contains("\"stats\":{")); - assertTrue(json.contains("\"datapoints\":400")); - assertTrue(json.contains("\"rawDatapoints\":800")); - assertTrue(json.contains("\"timeSeries\":2")); + assertTrue(json.contains("\"emittedDPs\":401")); //assert stats summary assertFalse(json.contains("{\"statsSummary\":{")); @@ -327,15 +321,16 @@ public void formatQueryAsyncV1woStatsWSummary() throws Exception { assertTrue(json.contains("\"metric\":\"system.cpu.user\",")); assertTrue(json.contains("\"1356998700\":1,")); assertTrue(json.contains("\"1357058700\":201")); - + //assert stats assertFalse(json.contains("\"stats\":{")); //assert stats summary assertTrue(json.contains("{\"statsSummary\":{")); - assertTrue(json.contains("\"serializationTime\":1500")); - assertTrue(json.contains("\"storageTime\":0")); - assertTrue(json.contains("\"timeTotal\":2500")); + assertTrue(json.contains("\"serializationTime\":")); + assertTrue(json.contains("\"processingPreWriteTime\":")); + assertTrue(json.contains("\"emittedDPs\":401")); + assertTrue(json.contains("\"queryIdx_00\":")); } @Test @@ -570,7 +565,7 @@ private TSQuery getTestQuery(final boolean show_stats, final boolean show_summar */ private void validateTestQuery(final TSQuery data_query) { data_query.validateAndSetQuery(); - data_query.setQueryStats(new QueryStats(remote, data_query)); + data_query.setQueryStats(new QueryStats(remote, data_query, null)); } /** @@ -590,6 +585,15 @@ public Long answer(InvocationOnMock invocation) throws Throwable { return ts; } }); + + PowerMockito.when(DateTime.nanoTime()) + .thenAnswer(new Answer () { + public Long answer(InvocationOnMock invocation) throws Throwable { + long ts = timestamp.get(0); + timestamp.set(0, ts + 500); + return ts * 1000000; + } + }); } } diff --git a/test/tsd/TestQueryRpc.java b/test/tsd/TestQueryRpc.java index b4f0f5caa1..dfd885fcb0 100644 --- a/test/tsd/TestQueryRpc.java +++ b/test/tsd/TestQueryRpc.java @@ -442,6 +442,7 @@ public void postQuerySimplePass() throws Exception { "{\"start\":1425440315306,\"queries\":" + "[{\"metric\":\"somemetric\",\"aggregator\":\"sum\",\"rate\":true," + "\"rateOptions\":{\"counter\":false}}]}"); + NettyMocks.mockChannelFuture(query); rpc.execute(tsdb, query); assertEquals(HttpResponseStatus.OK, query.response().getStatus()); } @@ -469,6 +470,7 @@ public void postQueryNoMetricBadRequest() throws Exception { public void executeEmpty() throws Exception { final HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?start=1h-ago&m=sum:sys.cpu.user"); + NettyMocks.mockChannelFuture(query); rpc.execute(tsdb, query); final String json = query.response().getContent().toString(Charset.forName("UTF-8")); @@ -484,6 +486,7 @@ public void execute() throws Exception { final HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query?start=1h-ago&m=sum:sys.cpu.user"); + NettyMocks.mockChannelFuture(query); rpc.execute(tsdb, query); final String json = query.response().getContent().toString(Charset.forName("UTF-8")); diff --git a/test/utils/TestDateTime.java b/test/utils/TestDateTime.java index 23867039c4..63ae2ffab8 100644 --- a/test/utils/TestDateTime.java +++ b/test/utils/TestDateTime.java @@ -17,6 +17,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.Mockito.when; import java.text.SimpleDateFormat; @@ -371,5 +372,33 @@ public void currentTimeMillis() { when(System.currentTimeMillis()).thenReturn(1388534400000L); assertEquals(1388534400000L, DateTime.currentTimeMillis()); } + + @Test + public void nanoTime() { + PowerMockito.mockStatic(System.class); + when(System.nanoTime()).thenReturn(1388534400000000000L); + assertEquals(1388534400000000000L, DateTime.nanoTime()); + } + + @Test + public void msFromNano() { + assertEquals(0, DateTime.msFromNano(0), 0.0001); + assertEquals(1, DateTime.msFromNano(1000000), 0.0001); + assertEquals(-1, DateTime.msFromNano(-1000000), 0.0001); + assertEquals(1.5, DateTime.msFromNano(1500000), 0.0001); + assertEquals(1.123, DateTime.msFromNano(1123000), 0.0001); + } + + @Test + public void msFromNanoDiff() { + assertEquals(0, DateTime.msFromNanoDiff(1000000, 1000000), 0.0001); + assertEquals(0.5, DateTime.msFromNanoDiff(1500000, 1000000), 0.0001); + assertEquals(1.5, DateTime.msFromNanoDiff(1500000, 0), 0.0001); + assertEquals(0.5, DateTime.msFromNanoDiff(-1000000, -1500000), 0.0001); + try { + assertEquals(0.5, DateTime.msFromNanoDiff(1000000, 1500000), 0.0001); + fail("Expected an IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } } From 48f079cd54e846ecd458a528406ad1c8be37795d Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sun, 14 Feb 2016 12:59:59 -0800 Subject: [PATCH 120/233] Rollback 6d2102af7d7391b759698979cab9ae74bea15d80 as I was mistaken in that on Linux hosts it did indeed point to the wrong config directory when installing locally and via package. We'll need to revisit this for FreeBSD. Signed-off-by: Chris Larsen --- Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index 15013eb61a..78abae4d5d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -455,7 +455,7 @@ printdeps: # This is kind of a hack, but I couldn't find a better way to adjust the paths # in the script before it gets installed... install-exec-hook: - script=tsdb; pkgdatadir='$(pkgdatadir)'; configdir='$(prefix)/etc/opentsdb'; \ + script=tsdb; pkgdatadir='$(pkgdatadir)'; configdir='$(sysconfigdir)/etc/opentsdb'; \ abs_srcdir=''; abs_builddir=''; $(edit_tsdb_script) cat tsdb.tmp >"$(DESTDIR)$(bindir)/tsdb" rm -f tsdb.tmp From ef4402c0678ac8e56dfd23c656f52832ce944387 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sun, 14 Feb 2016 12:35:58 -0800 Subject: [PATCH 121/233] Update the logback.xml config file with disabled configs for the query log. Also add the file appender to the /src/logback.xml file. Signed-off-by: Chris Larsen --- build-aux/deb/logback.xml | 33 +++++++++++++++++++-- build-aux/rpm/logback.xml | 33 +++++++++++++++++++-- src/logback.xml | 60 ++++++++++++++++++++++++++++++++++++--- 3 files changed, 118 insertions(+), 8 deletions(-) diff --git a/build-aux/deb/logback.xml b/build-aux/deb/logback.xml index 7f0fb57694..7ae2c3fcfc 100644 --- a/build-aux/deb/logback.xml +++ b/build-aux/deb/logback.xml @@ -9,10 +9,15 @@ + 1024 + /var/log/opentsdb/opentsdb.log true @@ -27,16 +32,40 @@ 128MB - %d{HH:mm:ss.SSS} %-5level [%logger{0}.%M] - %msg%n + + + + /var/log/opentsdb/queries.log + true + + + /var/log/opentsdb/queries.log.%i + 1 + 4 + + + + 128MB + + + %date{ISO8601} [%logger.%M] %msg%n + + + + + + + + diff --git a/build-aux/rpm/logback.xml b/build-aux/rpm/logback.xml index 7f0fb57694..7ae2c3fcfc 100644 --- a/build-aux/rpm/logback.xml +++ b/build-aux/rpm/logback.xml @@ -9,10 +9,15 @@ + 1024 + /var/log/opentsdb/opentsdb.log true @@ -27,16 +32,40 @@ 128MB - %d{HH:mm:ss.SSS} %-5level [%logger{0}.%M] - %msg%n + + + + /var/log/opentsdb/queries.log + true + + + /var/log/opentsdb/queries.log.%i + 1 + 4 + + + + 128MB + + + %date{ISO8601} [%logger.%M] %msg%n + + + + + + + + diff --git a/src/logback.xml b/src/logback.xml index b06776504a..ff97a50889 100644 --- a/src/logback.xml +++ b/src/logback.xml @@ -8,15 +8,67 @@ + + 1024 + + + + /var/log/opentsdb/opentsdb.log + true + + /home/y/logs/opentsdb2/opentsdb.log.%i + 1 + 4 + + + 512MB + + + + %date{ISO8601} [%thread] %-5level [%logger{0}.%M] - %msg%n + + + + + + /var/log/opentsdb/queries.log + true - - - - + + /var/log/opentsdb/queries.log.%i + 1 + 4 + + + + 128MB + + + %date{ISO8601} [%logger.%M] %msg%n + + + + + + + + + + + + + + + From eaf0fdc9e36666937241e153df06ea61f121ec18 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Tue, 16 Feb 2016 11:51:33 -0800 Subject: [PATCH 122/233] Removing the Zookeeper jar. How'd that get in there !?!?!? --- third_party/zookeeper/zookeeper-3.4.5.jar | Bin 779974 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 third_party/zookeeper/zookeeper-3.4.5.jar diff --git a/third_party/zookeeper/zookeeper-3.4.5.jar b/third_party/zookeeper/zookeeper-3.4.5.jar deleted file mode 100644 index a7966bbbce49344a67438bee8bb0cd1fd4952eee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 779974 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

N}hR{0dLySoh!!h~(WZKRed%U$2{V7gCf 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 z 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

r6T9k`3Qu3|?nk%Zsi+St;89-&5yV4X1>E zoDJQnyX@P`9Nut1-WX7A4*`AmaX!e)rTf?CzW}g)o?-QHiWO<>pzXaomcfFS@r&t{ubJP4f}1tTO>03`wv{?9;(f^B-g z9gN#)6Xq3_4-{H!@W0qfD~dFOCC8>_Ztl}-2M236{M~+_vxByvnv*A#w3wp>HU`Mj znzR!(SmpVZMWx1~BdTFCO$RaK@L^8eP`X)&ZH*+CFM_vP;RR4vSA-sgl?X4N+>8Dz z4_&AD0}Ux)3Ts9Ze}lRWM*s5a^K5x9pze|1z+059BH;3^?iWz3985K#4aQ$|UfhS5 z3qw5A-$VgecZ(A8i@buOCd3`)s}To!kx7E+zeHU&H}rQf{EAuRp){FkNl#Z4&4Gj| z5=bNm*;62{Y=y(f$K)W47}Y=Zo^-osWV45{P|FO)$Cu|AEe&cDwzfX9 zYQ!%XmX~F8Mk-@*$nEyg2gZLqrWE4;u7X~7JU4FFI*j(O`Ey8wJ0;2+OK8p~z9nT@ z&gvlZBJ%P_-e6MQvc=3P*tuCCwMswBXkHoD;~?j>jX$@c{%{Dh!5D!J6sx}FK$3a+ za%MtGz*H?75@3f9n{^)A~eYczgYw^+R>kWAm*;*b_h8)0I5-lQ5IU_M`6A-SErv z%xOrkB)KJJLdnk3yOTvi^YymJ)>K+Ptfo`RY<--M*Q9&B-sw9Hipvu_fP(vF&$*DEXtO_~EqqrtE zDVIk(1_V~6y9TDys)-scoQYYiU3=bxzuOi4>TtPl+NZ=A85m z&ATFuzj4uIGTsTM9H?qhrH^q+jbg*9y*EX|JFG^^mUTh8N+qMlY|W~rm$ofnQ7mx#u*7=N>_xraf1V4LpoqXKsyC0$_r!qcyXrB= zy6S<}wY!EMuoSA-u4>z|q^7M)YK*aQj-vUcq^+A;K(DTDQckm^8gytSt!rFmZFchRgJ%XN#3yh+`Pq0&CB1eN?+nFyTtFTM1Jw1N$ct*3eE#^%XeyUVVMeNbU1+0%aQ9I?eq zyYgoBTvxlxyy}JSDKO2u0ug*qyyd+pnPB5wKJ~pZ%~=G4c}d>}yugq6{fC_aQ6UeC z%0D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 z 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≥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%0D>1+d;giZJ7s_0K3j_X9S~I1 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 HIGX Date: Sun, 21 Feb 2016 14:26:19 -0800 Subject: [PATCH 123/233] Add HBase appends and iddle connections closed stats. Signed-off-by: Chris Larsen --- src/core/TSDB.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/TSDB.java b/src/core/TSDB.java index 900f038128..5e3f9657f7 100644 --- a/src/core/TSDB.java +++ b/src/core/TSDB.java @@ -553,12 +553,14 @@ public void collectStats(final StatsCollector collector) { collector.record("hbase.rpcs", stats.deletes(), "type=delete"); collector.record("hbase.rpcs", stats.gets(), "type=get"); collector.record("hbase.rpcs", stats.puts(), "type=put"); + collector.record("hbase.rpcs", stats.appends(), "type=append"); collector.record("hbase.rpcs", stats.rowLocks(), "type=rowLock"); collector.record("hbase.rpcs", stats.scannersOpened(), "type=openScanner"); collector.record("hbase.rpcs", stats.scans(), "type=scan"); collector.record("hbase.rpcs.batched", stats.numBatchedRpcSent()); collector.record("hbase.flushes", stats.flushes()); collector.record("hbase.connections.created", stats.connectionsCreated()); + collector.record("hbase.connections.idle_closed", stats.idleConnectionsClosed()); collector.record("hbase.nsre", stats.noSuchRegionExceptions()); collector.record("hbase.nsre.rpcs_delayed", stats.numRpcDelayedDueToNSRE()); From 01feeaf1c9024af4c94b29af80b1ff51bf8d8fba Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sat, 13 Feb 2016 22:39:28 -0800 Subject: [PATCH 124/233] Comment out the QueryExecutor class lines where the calls to QueryStats object have changed. Need to fix that up long term Signed-off-by: Chris Larsen --- src/tsd/QueryExecutor.java | 61 +++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/src/tsd/QueryExecutor.java b/src/tsd/QueryExecutor.java index 9a5683f01b..90cdca0165 100644 --- a/src/tsd/QueryExecutor.java +++ b/src/tsd/QueryExecutor.java @@ -206,7 +206,7 @@ public QueryExecutor(final TSDB tsdb, final Query query) { public void execute(final HttpQuery query) { http_query = query; final QueryStats query_stats = - new QueryStats(query.getRemoteAddress(), ts_query); + new QueryStats(query.getRemoteAddress(), ts_query, query.getHeaders()); ts_query.setQueryStats(query_stats); final long start = DateTime.currentTimeMillis(); @@ -241,7 +241,6 @@ class QueriesCB implements Callback> { public Object call(final ArrayList query_results) throws Exception { - query_stats.setTimeStorage(DateTime.currentTimeMillis() - start); for (int i = 0; i < query_results.size(); i++) { final TSSubQuery sub = ts_query.getQueries().get(i); @@ -436,27 +435,27 @@ public ChannelBuffer call(final Object obj) throws Exception { json.writeEndArray(); - ts_query.getQueryStats().setTimeSerialization( - DateTime.currentTimeMillis() - start); - ts_query.getQueryStats().markComplete(); +// ts_query.getQueryStats().setTimeSerialization( +// DateTime.currentTimeMillis() - start); + ts_query.getQueryStats().markSerializationSuccessful(); // dump overall stats as an extra object in the array - if (true) { - final QueryStats stats = ts_query.getQueryStats(); - json.writeFieldName("statsSummary"); - json.writeStartObject(); - //json.writeStringField("hostname", TSDB.getHostname()); - //json.writeNumberField("runningQueries", stats.getNumRunningQueries()); - json.writeNumberField("datapoints", stats.getAggregatedSize()); - json.writeNumberField("rawDatapoints", stats.getSize()); - //json.writeNumberField("rowsFetched", stats.getRowsFetched()); - json.writeNumberField("aggregationTime", stats.getTimeAggregation()); - json.writeNumberField("serializationTime", stats.getTimeSerialization()); - json.writeNumberField("storageTime", stats.getTimeStorage()); - json.writeNumberField("timeTotal", - ((double)stats.getTimeTotal() / (double)1000000)); - json.writeEndObject(); - } +// if (true) { +// final QueryStats stats = ts_query.getQueryStats(); +// json.writeFieldName("statsSummary"); +// json.writeStartObject(); +// //json.writeStringField("hostname", TSDB.getHostname()); +// //json.writeNumberField("runningQueries", stats.getNumRunningQueries()); +// json.writeNumberField("datapoints", stats.getAggregatedSize()); +// json.writeNumberField("rawDatapoints", stats.getSize()); +// //json.writeNumberField("rowsFetched", stats.getRowsFetched()); +// json.writeNumberField("aggregationTime", stats.getTimeAggregation()); +// json.writeNumberField("serializationTime", stats.getTimeSerialization()); +// json.writeNumberField("storageTime", stats.getTimeStorage()); +// json.writeNumberField("timeTotal", +// ((double)stats.getTimeTotal() / (double)1000000)); +// json.writeEndObject(); +// } // dump the original query if (true) { @@ -488,29 +487,29 @@ public Object call(final Exception e) throws Exception { if (ex != null) { LOG.error("Unexpected exception: ", ex); // TODO - find a better way to determine the real error - QueryExecutor.this.ts_query.getQueryStats() - .markComplete(HttpResponseStatus.BAD_REQUEST, ex); +// QueryExecutor.this.ts_query.getQueryStats() +// .markComplete(HttpResponseStatus.BAD_REQUEST, ex); QueryExecutor.this.http_query.badRequest(new BadRequestException(ex)); } else { LOG.error("The deferred group exception didn't have a cause???"); - QueryExecutor.this.ts_query.getQueryStats() - .markComplete(HttpResponseStatus.INTERNAL_SERVER_ERROR, ex); +// QueryExecutor.this.ts_query.getQueryStats() +// .markComplete(HttpResponseStatus.INTERNAL_SERVER_ERROR, ex); QueryExecutor.this.http_query.badRequest(new BadRequestException(e)); } } else if (e.getClass() == QueryException.class) { - QueryExecutor.this.ts_query.getQueryStats() - .markComplete(HttpResponseStatus.REQUEST_TIMEOUT, e); +// QueryExecutor.this.ts_query.getQueryStats() +// .markComplete(HttpResponseStatus.REQUEST_TIMEOUT, e); QueryExecutor.this.http_query.badRequest(new BadRequestException((QueryException)e)); } else { - QueryExecutor.this.ts_query.getQueryStats() - .markComplete(HttpResponseStatus.INTERNAL_SERVER_ERROR, e); +// QueryExecutor.this.ts_query.getQueryStats() +// .markComplete(HttpResponseStatus.INTERNAL_SERVER_ERROR, e); QueryExecutor.this.http_query.badRequest(new BadRequestException(e)); } return null; } catch (RuntimeException ex) { LOG.error("Exception thrown during exception handling", ex); - QueryExecutor.this.ts_query.getQueryStats() - .markComplete(HttpResponseStatus.INTERNAL_SERVER_ERROR, ex); +// QueryExecutor.this.ts_query.getQueryStats() +// .markComplete(HttpResponseStatus.INTERNAL_SERVER_ERROR, ex); QueryExecutor.this.http_query.sendReply(HttpResponseStatus.INTERNAL_SERVER_ERROR, ex.getMessage().getBytes()); return null; From 629fc558d15d357805059ee12e3a8077eef56d92 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Mon, 22 Feb 2016 19:15:52 -0800 Subject: [PATCH 125/233] Fix up the expression UTs now that the response expects a callback from the Netty channel. Signed-off-by: Chris Larsen --- src/core/TSSubQuery.java | 4 +++- test/tsd/TestQueryExecutor.java | 21 ++++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/core/TSSubQuery.java b/src/core/TSSubQuery.java index 8ed1c23bce..f7a3e35177 100644 --- a/src/core/TSSubQuery.java +++ b/src/core/TSSubQuery.java @@ -288,7 +288,9 @@ public ByteSet getFilterTagKs() { } final ByteSet tagks = new ByteSet(); for (final TagVFilter filter : filters) { - tagks.add(filter.getTagkBytes()); + if (filter != null && filter.getTagkBytes() != null) { + tagks.add(filter.getTagkBytes()); + } } return tagks; } diff --git a/test/tsd/TestQueryExecutor.java b/test/tsd/TestQueryExecutor.java index a1a021156b..eeabd47b3b 100644 --- a/test/tsd/TestQueryExecutor.java +++ b/test/tsd/TestQueryExecutor.java @@ -87,8 +87,9 @@ public void oneExpressionWithOutputAlias() throws Exception { final QueryRpc rpc = new QueryRpc(); final HttpQuery query = NettyMocks.postQuery(tsdb, "/api/query/exp", json); - + NettyMocks.mockChannelFuture(query); rpc.execute(tsdb, query); + final String response = query.response().getContent().toString(Charset.forName("UTF-8")); assertTrue(response.contains("\"alias\":\"A plus B\"")); @@ -111,6 +112,7 @@ public void oneExpressionDefaultOutput() throws Exception { final QueryRpc rpc = new QueryRpc(); final HttpQuery query = NettyMocks.postQuery(tsdb, "/api/query/exp", json); + NettyMocks.mockChannelFuture(query); rpc.execute(tsdb, query); final String response = @@ -140,6 +142,7 @@ public void oneExpressionOutputAndBAlso() throws Exception { final QueryRpc rpc = new QueryRpc(); final HttpQuery query = NettyMocks.postQuery(tsdb, "/api/query/exp", json); + NettyMocks.mockChannelFuture(query); rpc.execute(tsdb, query); final String response = @@ -169,6 +172,7 @@ public void oneExpressionDefaultFill() throws Exception { final HttpQuery query = NettyMocks.postQuery(tsdb, "/api/query/exp", json); query.getQueryBaseRoute(); // to the correct serializer + NettyMocks.mockChannelFuture(query); rpc.execute(tsdb, query); final String response = @@ -200,6 +204,7 @@ public void twoExpressionsDefaultOutput() throws Exception { final QueryRpc rpc = new QueryRpc(); final HttpQuery query = NettyMocks.postQuery(tsdb, "/api/query/exp", json); + NettyMocks.mockChannelFuture(query); rpc.execute(tsdb, query); final String response = @@ -245,6 +250,7 @@ public void twoExpressionsOneWithoutResultsDefaultOutput() throws Exception { final HttpQuery query = NettyMocks.postQuery(tsdb, "/api/query/exp", json); query.getQueryBaseRoute(); // to the correct serializer + NettyMocks.mockChannelFuture(query); rpc.execute(tsdb, query); final String response = @@ -277,6 +283,7 @@ public void multiExpressionsOneOutput() throws Exception { final HttpQuery query = NettyMocks.postQuery(tsdb, "/api/query/exp", json); query.getQueryBaseRoute(); // to the correct serializer + NettyMocks.mockChannelFuture(query); rpc.execute(tsdb, query); final String response = @@ -305,6 +312,7 @@ public void nestedExpressionsOneLevelDefaultOutput() throws Exception { final QueryRpc rpc = new QueryRpc(); final HttpQuery query = NettyMocks.postQuery(tsdb, "/api/query/exp", json); + NettyMocks.mockChannelFuture(query); rpc.execute(tsdb, query); final String response = @@ -339,6 +347,7 @@ public void nestedExpressionsTwoLevelsDefaultOutput() throws Exception { final QueryRpc rpc = new QueryRpc(); final HttpQuery query = NettyMocks.postQuery(tsdb, "/api/query/exp", json); + NettyMocks.mockChannelFuture(query); rpc.execute(tsdb, query); final String response = @@ -379,6 +388,7 @@ public void nestedExpressionsTwoLevelsDefaultOutputOrdering() throws Exception { final QueryRpc rpc = new QueryRpc(); final HttpQuery query = NettyMocks.postQuery(tsdb, "/api/query/exp", json); + NettyMocks.mockChannelFuture(query); rpc.execute(tsdb, query); final String response = @@ -410,6 +420,7 @@ public void emptyResultSet() throws Exception { final HttpQuery query = NettyMocks.postQuery(tsdb, "/api/query/exp", json); query.getQueryBaseRoute(); // to the correct serializer + NettyMocks.mockChannelFuture(query); rpc.execute(tsdb, query); final String response = @@ -431,6 +442,7 @@ public void scannerException() throws Exception { final HttpQuery query = NettyMocks.postQuery(tsdb, "/api/query/exp", json); query.getQueryBaseRoute(); // to the correct serializer + NettyMocks.mockChannelFuture(query); rpc.execute(tsdb, query); final String response = @@ -453,6 +465,7 @@ public void nsunMetric() throws Exception { final HttpQuery query = NettyMocks.postQuery(tsdb, "/api/query/exp", json); query.getQueryBaseRoute(); // to the correct serializer + NettyMocks.mockChannelFuture(query); rpc.execute(tsdb, query); final String response = @@ -479,6 +492,7 @@ public void selfReferencingExpression() throws Exception { final HttpQuery query = NettyMocks.postQuery(tsdb, "/api/query/exp", json); query.getQueryBaseRoute(); // to the correct serializer + NettyMocks.mockChannelFuture(query); rpc.execute(tsdb, query); final String response = @@ -504,6 +518,7 @@ public void circularReferenceExpression() throws Exception { final HttpQuery query = NettyMocks.postQuery(tsdb, "/api/query/exp", json); query.getQueryBaseRoute(); // to the correct serializer + NettyMocks.mockChannelFuture(query); rpc.execute(tsdb, query); final String response = @@ -521,6 +536,7 @@ public void noIntersectionsFound() throws Exception { final HttpQuery query = NettyMocks.postQuery(tsdb, "/api/query/exp", json); query.getQueryBaseRoute(); // to the correct serializer + NettyMocks.mockChannelFuture(query); rpc.execute(tsdb, query); final String response = @@ -553,6 +569,7 @@ public void noIntersectionsFoundNestedExpression() throws Exception { final HttpQuery query = NettyMocks.postQuery(tsdb, "/api/query/exp", json); query.getQueryBaseRoute(); // to the correct serializer + NettyMocks.mockChannelFuture(query); rpc.execute(tsdb, query); final String response = @@ -574,6 +591,7 @@ public void noIntersectionsFoundOneMetricEmpty() throws Exception { final HttpQuery query = NettyMocks.postQuery(tsdb, "/api/query/exp", json); query.getQueryBaseRoute(); // to the correct serializer + NettyMocks.mockChannelFuture(query); rpc.execute(tsdb, query); final String response = @@ -592,6 +610,7 @@ public void notEnoughMetrics() throws Exception { final HttpQuery query = NettyMocks.postQuery(tsdb, "/api/query/exp", json); query.getQueryBaseRoute(); // to the correct serializer + NettyMocks.mockChannelFuture(query); rpc.execute(tsdb, query); final String response = From 320ee4468fcf33be33d51533151e3aa0b0f66a6a Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Tue, 23 Feb 2016 13:17:25 -0800 Subject: [PATCH 126/233] Fix gexp query unit test where the channel future wasn't mocked. Signed-off-by: Chris Larsen --- test/tsd/TestQueryRpc.java | 1 + 1 file changed, 1 insertion(+) diff --git a/test/tsd/TestQueryRpc.java b/test/tsd/TestQueryRpc.java index dfd885fcb0..666e3ac399 100644 --- a/test/tsd/TestQueryRpc.java +++ b/test/tsd/TestQueryRpc.java @@ -549,6 +549,7 @@ public void gexp() throws Exception { final HttpQuery query = NettyMocks.getQuery(tsdb, "/api/query/gexp?start=1h-ago&exp=scale(sum:sys.cpu.user,1)"); + NettyMocks.mockChannelFuture(query); rpc.execute(tsdb, query); assertEquals(query.response().getStatus(), HttpResponseStatus.OK); final String json = From b2c987a0c9e3fdfcf6ca454a7d589b79f347ffa2 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Tue, 23 Feb 2016 18:16:21 -0800 Subject: [PATCH 127/233] Add ignore unknown flags for Jackson to the TSQuery and TSSubQuery classes so we can parse forward and backward compatible queries. Signed-off-by: Chris Larsen --- src/core/TSQuery.java | 2 ++ src/core/TSSubQuery.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/core/TSQuery.java b/src/core/TSQuery.java index ccdd48abf2..6ae15fc789 100644 --- a/src/core/TSQuery.java +++ b/src/core/TSQuery.java @@ -18,6 +18,7 @@ import java.util.Map; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.google.common.base.Objects; import com.stumbleupon.async.Callback; import com.stumbleupon.async.Deferred; @@ -38,6 +39,7 @@ * {@code start_time} and {@code end_time} fields. * @since 2.0 */ +@JsonIgnoreProperties(ignoreUnknown = true) public final class TSQuery { /** User given start date/time, could be relative or absolute */ diff --git a/src/core/TSSubQuery.java b/src/core/TSSubQuery.java index f7a3e35177..8985d6f9cb 100644 --- a/src/core/TSSubQuery.java +++ b/src/core/TSSubQuery.java @@ -22,6 +22,7 @@ import net.opentsdb.query.filter.TagVFilter; import net.opentsdb.utils.ByteSet; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.google.common.base.Objects; import com.google.common.collect.ImmutableMap; @@ -42,6 +43,7 @@ * {@code agg} and {@code downsample_specifier} fields. * @since 2.0 */ +@JsonIgnoreProperties(ignoreUnknown = true) public final class TSSubQuery { /** User given name of an aggregation function to use */ private String aggregator; From dc387ab27c3a5c62f520e8b0e02ed685de0ee425 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sat, 13 Feb 2016 14:05:29 -0800 Subject: [PATCH 128/233] Add an explicit tags flag to queries so series that only have the tags listed in the filters will be returned. This helps to avoid picking up series with more or fewer tags on accident. Also implemnt the Fuzzy Row Filter for any explicit tag query based on the work of @junegunn in pr #588. This allows HBase to perform skip-scan operations and help improve queries TREMENDOUSLY when looking for a small subset of time series in a high-cardinality set. E.g. if you have hundreds of hosts for a metric but only want a few of them. Signed-off-by: Chris Larsen --- src/core/Const.java | 17 +++- src/core/Internal.java | 11 +++ src/core/TSSubQuery.java | 28 +++++- src/core/TsdbQuery.java | 35 +++++-- src/query/QueryUtil.java | 143 +++++++++++++++++++++++++++- src/tsd/QueryRpc.java | 2 + src/utils/Config.java | 1 + test/core/TestTSSubQuery.java | 21 +++- test/core/TestTsdbQueryQueries.java | 95 ++++++++++++++++++ test/storage/MockBase.java | 87 +++++++++++++++-- test/tsd/TestQueryRpc.java | 49 ++++++++++ 11 files changed, 461 insertions(+), 28 deletions(-) diff --git a/src/core/Const.java b/src/core/Const.java index d9f97c2aab..6369589354 100644 --- a/src/core/Const.java +++ b/src/core/Const.java @@ -12,6 +12,9 @@ // see . package net.opentsdb.core; +import java.nio.charset.Charset; +import java.util.TimeZone; + /** Constants used in various places. */ public final class Const { @@ -38,7 +41,19 @@ static void setMaxNumTags(final short tags) { } MAX_NUM_TAGS = tags; } - + + /** The default ASCII character set for encoding tables and qualifiers that + * don't depend on user input that may be encoded with UTF. + * Charset to use with our server-side row-filter. + * We use this one because it preserves every possible byte unchanged. + */ + public static final Charset ASCII_CHARSET = Charset.forName("ISO-8859-1"); + + /** Used for metrics, tags names and tag values */ + public static final Charset UTF8_CHARSET = Charset.forName("UTF8"); + + /** The UTC timezone used for rollup and calendar conversions */ + public static final TimeZone UTC_TZ = TimeZone.getTimeZone("UTC"); /** Number of LSBs in time_deltas reserved for flags. */ public static final short FLAG_BITS = 4; diff --git a/src/core/Internal.java b/src/core/Internal.java index 733dc04859..5a40e073bf 100644 --- a/src/core/Internal.java +++ b/src/core/Internal.java @@ -123,6 +123,17 @@ public static long baseTime(final long timestamp) { } } + /** + * Sets the time in a raw data table row key + * @param row The row to modify + * @param base_time The base time to store + * @since 2.3 + */ + public static void setBaseTime(final byte[] row, int base_time) { + Bytes.setInt(row, base_time, Const.SALT_WIDTH() + + TSDB.metrics_width()); + } + /** @see Tags#getTags */ public static Map getTags(final TSDB tsdb, final byte[] row) { return Tags.getTags(tsdb, row); diff --git a/src/core/TSSubQuery.java b/src/core/TSSubQuery.java index 8985d6f9cb..bff500a75f 100644 --- a/src/core/TSSubQuery.java +++ b/src/core/TSSubQuery.java @@ -73,6 +73,9 @@ public final class TSSubQuery { * tags map. In the future we'll have special JSON objects for them. */ private List filters; + /** Whether or not to match series with ONLY the given tags */ + private boolean explicit_tags; + /** Index of the sub query */ private int index; @@ -89,7 +92,7 @@ public int hashCode() { // NOTE: Do not add any non-user submitted variables to the hash. We don't // want the hash to change after validation. return Objects.hashCode(aggregator, metric, tsuids, downsample, rate, - rate_options, filters); + rate_options, filters, explicit_tags); } @Override @@ -113,7 +116,8 @@ public boolean equals(final Object obj) { && Objects.equal(downsample, query.downsample) && Objects.equal(rate, query.rate) && Objects.equal(rate_options, query.rate_options) - && Objects.equal(filters, query.filters); + && Objects.equal(filters, query.filters) + && Objects.equal(explicit_tags, query.explicit_tags); } public String toString() { @@ -151,8 +155,12 @@ public String toString() { .append(", rate=") .append(rate) .append(", rate_options=") - .append(rate_options); - buf.append(")"); + .append(rate_options) + .append(", explicit_tags=") + .append("explicit_tags") + .append(", index=") + .append(index) + .append(")"); return buf.toString(); } @@ -297,6 +305,12 @@ public ByteSet getFilterTagKs() { return tagks; } + /** @return whether or not to match series with ONLY the given tags + * @since 2.3 */ + public boolean getExplicitTags() { + return explicit_tags; + } + /** @return the index of the sub query * @since 2.3 */ public int getIndex() { @@ -351,6 +365,12 @@ public void setFilters(List filters) { this.filters = filters; } + /** @param whether or not to match series with ONLY the given tags + * @since 2.3 */ + public void setExplicitTags(final boolean explicit_tags) { + this.explicit_tags = explicit_tags; + } + /** @param index the index of the sub query * @since 2.3 */ public void setIndex(final int index) { diff --git a/src/core/TsdbQuery.java b/src/core/TsdbQuery.java index 5d96c4e1c3..8ea2f48509 100644 --- a/src/core/TsdbQuery.java +++ b/src/core/TsdbQuery.java @@ -95,6 +95,9 @@ final class TsdbQuery implements Query { /** Row key regex to pass to HBase if we have tags or TSUIDs */ private String regex; + /** Whether or not to enable the fuzzy row filter for Hbase */ + private boolean enable_fuzzy_filter; + /** * Tags by which we must group the results. * Each element is a tag ID. @@ -140,10 +143,14 @@ final class TsdbQuery implements Query { /** An object for storing stats in regarding the query. May be null */ private QueryStats query_stats; + /** Whether or not to match series with ONLY the given tags */ + private boolean explicit_tags; + /** Constructor. */ public TsdbQuery(final TSDB tsdb) { this.tsdb = tsdb; - + enable_fuzzy_filter = tsdb.getConfig() + .getBoolean("tsd.query.enable_fuzzy_filter"); // By default, we should interpolate. fill_policy = DownsamplingSpecification.DEFAULT_FILL_POLICY; } @@ -307,6 +314,14 @@ public void setTimeSeries(final List tsuids, this.rate_options = rate_options; } + /** + * @param explicit_tags Whether or not to match only on the given tags + * @since 2.3 + */ + public void setExplicitTags(final boolean explicit_tags) { + this.explicit_tags = explicit_tags; + } + public Deferred configureFromQuery(final TSQuery query, final int index) { if (query.getQueries() == null || query.getQueries().isEmpty()) { @@ -334,6 +349,7 @@ public Deferred configureFromQuery(final TSQuery query, sample_interval_ms = sub_query.downsampleInterval(); fill_policy = sub_query.fillPolicy(); filters = sub_query.getFilters(); + explicit_tags = sub_query.getExplicitTags(); // if we have tsuids set, that takes precedence if (sub_query.getTsuids() != null && !sub_query.getTsuids().isEmpty()) { @@ -555,8 +571,11 @@ private Deferred> findSpans() throws HBaseException { delete, query_stats, query_index).scan(); } - scan_start_time = DateTime.nanoTime(); + scan_start_time = DateTime.nanoTime(); final Scanner scanner = getScanner(); + if (query_stats != null) { + query_stats.addScannerId(query_index, 0, scanner.toString()); + } final Deferred> results = new Deferred>(); @@ -1042,13 +1061,11 @@ private long getScanEndTimeSeconds() { * @param scanner The scanner on which to add the filter. */ private void createAndSetFilter(final Scanner scanner) { - if (regex == null) { - regex = QueryUtil.getRowKeyUIDRegex(group_bys, row_key_literals); - } - scanner.setKeyRegexp(regex, CHARSET); - if (LOG.isDebugEnabled()) { - LOG.debug("Scanner regex: " + QueryUtil.byteRegexToString(regex)); - } + QueryUtil.setDataTableScanFilter(scanner, group_bys, row_key_literals, + explicit_tags, enable_fuzzy_filter, + (end_time == UNSET + ? -1 // Will scan until the end (0xFFF...). + : (int) getScanEndTimeSeconds())); } /** diff --git a/src/query/QueryUtil.java b/src/query/QueryUtil.java index 1fc1363632..994ce86214 100644 --- a/src/query/QueryUtil.java +++ b/src/query/QueryUtil.java @@ -20,12 +20,19 @@ import java.util.Map.Entry; import net.opentsdb.core.Const; +import net.opentsdb.core.Internal; import net.opentsdb.core.RowKey; import net.opentsdb.core.TSDB; import net.opentsdb.uid.UniqueId; import org.hbase.async.Bytes; +import org.hbase.async.FilterList; +import org.hbase.async.FuzzyRowFilter; +import org.hbase.async.KeyRegexpFilter; import org.hbase.async.Bytes.ByteMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.hbase.async.ScanFilter; import org.hbase.async.Scanner; /** @@ -34,6 +41,7 @@ * @since 2.2 */ public class QueryUtil { + private static final Logger LOG = LoggerFactory.getLogger(QueryUtil.class); /** * Crafts a regular expression for scanning over data table rows and filtering @@ -48,9 +56,35 @@ public class QueryUtil { */ public static String getRowKeyUIDRegex(final List group_bys, final ByteMap row_key_literals) { + return getRowKeyUIDRegex(group_bys, row_key_literals, false, null, null); + } + + /** + * Crafts a regular expression for scanning over data table rows and filtering + * time series that the user doesn't want. Also fills in an optional fuzzy + * mask and key as it builds the regex if configured to do so. + * @param group_bys An optional list of tag keys that we want to group on. May + * be null. + * @param row_key_literals An optional list of key value pairs to filter on. + * May be null. + * @param explicit_tags Whether or not explicit tags are enabled so that the + * regex only picks out series with the specified tags + * @param fuzzy_key An optional fuzzy filter row key + * @param fuzzy_mask An optional fuzzy filter mask + * @return A regular expression string to pass to the storage layer. + * @since 2.3 + */ + public static String getRowKeyUIDRegex( + final List group_bys, + final ByteMap row_key_literals, + final boolean explicit_tags, + final byte[] fuzzy_key, + final byte[] fuzzy_mask) { if (group_bys != null) { Collections.sort(group_bys, Bytes.MEMCMP); } + final int prefix_width = Const.SALT_WIDTH() + TSDB.metrics_width() + + Const.TIMESTAMP_BYTES; final short name_width = TSDB.tagk_width(); final short value_width = TSDB.tagv_width(); final short tagsize = (short) (name_width + value_width); @@ -73,7 +107,14 @@ public static String getRowKeyUIDRegex(final List group_bys, final Iterator> it = row_key_literals == null ? new ByteMap().iterator() : row_key_literals.iterator(); - + int fuzzy_offset = Const.SALT_WIDTH() + TSDB.metrics_width(); + if (fuzzy_mask != null) { + // make sure to skip the timestamp when scanning + while (fuzzy_offset < prefix_width) { + fuzzy_mask[fuzzy_offset++] = 1; + } + } + while(it.hasNext()) { Entry entry = it.hasNext() ? it.next() : null; // TODO - This look ahead may be expensive. We need to get some data around @@ -83,7 +124,19 @@ public static String getRowKeyUIDRegex(final List group_bys, entry.getValue() != null && entry.getValue().length == 0; // Skip any number of tags. - buf.append("(?:.{").append(tagsize).append("})*"); + if (!explicit_tags) { + buf.append("(?:.{").append(tagsize).append("})*"); + } else if (fuzzy_mask != null) { + // TODO - see if we can figure out how to improve the fuzzy filter by + // setting explicit tag values whenever we can. In testing there was + // a conflict between the row key regex and fuzzy filter that prevented + // results from returning properly. + System.arraycopy(entry.getKey(), 0, fuzzy_key, fuzzy_offset, name_width); + fuzzy_offset += name_width; + for (int i = 0; i < value_width; i++) { + fuzzy_mask[fuzzy_offset++] = 1; + } + } if (not_key) { // start the lookahead as we have a key we explicitly do not want in the // results @@ -115,10 +168,94 @@ public static String getRowKeyUIDRegex(final List group_bys, } } // Skip any number of tags before the end. - buf.append("(?:.{").append(tagsize).append("})*$"); + if (!explicit_tags) { + buf.append("(?:.{").append(tagsize).append("})*"); + } + buf.append("$"); return buf.toString(); } + /** + * Sets a filter or filter list on the scanner based on whether or not the + * query had tags it needed to match. + * @param scanner The scanner to modify. + * @param group_bys An optional list of tag keys that we want to group on. May + * be null. + * @param row_key_literals An optional list of key value pairs to filter on. + * May be null. + * @param explicit_tag sWhether or not explicit tags are enabled so that the + * regex only picks out series with the specified tags + * @param enable_fuzzy_filter Whether or not a fuzzy filter should be used + * in combination with the explicit tags param. If explicit tags is disabled + * then this param is ignored. + * @param end_time The end of the query time so the fuzzy filter knows when + * to stop scanning. + */ + public static void setDataTableScanFilter( + final Scanner scanner, + final List group_bys, + final ByteMap row_key_literals, + final boolean explicit_tags, + final boolean enable_fuzzy_filter, + final int end_time) { + + // no-op + if (group_bys.isEmpty() && row_key_literals.isEmpty()) { + return; + } + + final int prefix_width = Const.SALT_WIDTH() + TSDB.metrics_width() + + Const.TIMESTAMP_BYTES; + final short name_width = TSDB.tagk_width(); + final short value_width = TSDB.tagv_width(); + final byte[] fuzzy_key; + final byte[] fuzzy_mask; + if (explicit_tags && enable_fuzzy_filter) { + fuzzy_key = new byte[prefix_width + (row_key_literals.size() * + (name_width + value_width))]; + fuzzy_mask = new byte[prefix_width + (row_key_literals.size() * + (name_width + value_width))]; + System.arraycopy(scanner.getCurrentKey(), 0, fuzzy_key, 0, + scanner.getCurrentKey().length); + } else { + fuzzy_key = fuzzy_mask = null; + } + + final String regex = getRowKeyUIDRegex(group_bys, row_key_literals, + explicit_tags, fuzzy_key, fuzzy_mask); + final KeyRegexpFilter regex_filter = new KeyRegexpFilter( + regex.toString(), Const.ASCII_CHARSET); + if (LOG.isDebugEnabled()) { + LOG.debug("Regex for scanner: " + scanner + ": " + + byteRegexToString(regex)); + } + + if (!explicit_tags || !enable_fuzzy_filter) { + scanner.setFilter(regex_filter); + return; + } + + scanner.setStartKey(fuzzy_key); + final byte[] stop_key = Arrays.copyOf(fuzzy_key, fuzzy_key.length); + Internal.setBaseTime(stop_key, end_time); + int idx = Const.SALT_WIDTH() + TSDB.metrics_width() + + Const.TIMESTAMP_BYTES + TSDB.tagk_width(); + // max out the tag values + while (idx < stop_key.length) { + for (int i = 0; i < TSDB.tagv_width(); i++) { + stop_key[idx++] = (byte) 0xFF; + } + idx += TSDB.tagk_width(); + } + scanner.setStopKey(stop_key); + final List filters = new ArrayList(2); + filters.add( + new FuzzyRowFilter( + new FuzzyRowFilter.FuzzyFilterPair(fuzzy_key, fuzzy_mask))); + filters.add(regex_filter); + scanner.setFilter(new FilterList(filters)); + } + /** * Creates a regular expression with a list of or'd TUIDs to compare * against the rows in storage. diff --git a/src/tsd/QueryRpc.java b/src/tsd/QueryRpc.java index a9e87566c2..9a321025d7 100644 --- a/src/tsd/QueryRpc.java +++ b/src/tsd/QueryRpc.java @@ -630,6 +630,8 @@ private static void parseMTypeSubQuery(final String query_string, } } else if (Character.isDigit(parts[x].charAt(0))) { sub_query.setDownsample(parts[x]); + } else if (parts[x].toLowerCase().startsWith("explicit_tags")) { + sub_query.setExplicitTags(true); } } diff --git a/src/utils/Config.java b/src/utils/Config.java index f1ff0e03f1..4e8cb9ed02 100644 --- a/src/utils/Config.java +++ b/src/utils/Config.java @@ -501,6 +501,7 @@ protected void setDefaults() { default_map.put("tsd.query.filter.expansion_limit", "4096"); default_map.put("tsd.query.skip_unresolved_tagvs", "false"); default_map.put("tsd.query.allow_simultaneous_duplicates", "true"); + default_map.put("tsd.query.enable_fuzzy_filter", "true"); default_map.put("tsd.rtpublisher.enable", "false"); default_map.put("tsd.rtpublisher.plugin", ""); default_map.put("tsd.search.enable", "false"); diff --git a/test/core/TestTSSubQuery.java b/test/core/TestTSSubQuery.java index 675dc1ceb1..92cd5661a8 100644 --- a/test/core/TestTSSubQuery.java +++ b/test/core/TestTSSubQuery.java @@ -12,7 +12,6 @@ // see . package net.opentsdb.core; -import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -30,7 +29,6 @@ import net.opentsdb.query.filter.TagVWildcardFilter; import org.junit.Test; -import org.powermock.reflect.Whitebox; public final class TestTSSubQuery { @@ -555,6 +553,25 @@ public void testHashCodeandEqualsRateOptionsNull() { assertFalse(sub1 == sub2); } + @Test + public void testHashCodeandEqualsExplicitTags() { + final TSSubQuery sub1 = getBaseQuery(); + final int hash_a = sub1.hashCode(); + + sub1.setExplicitTags(true); + final int hash_b = sub1.hashCode(); + assertFalse(hash_a == sub1.hashCode()); + sub1.validateAndSetQuery(); + assertEquals(hash_b, sub1.hashCode()); + + TSSubQuery sub2 = getBaseQuery(); + sub2.setExplicitTags(true); + + assertEquals(hash_b, sub2.hashCode()); + assertEquals(sub1, sub2); + assertFalse(sub1 == sub2); + } + @Test public void testEqualsNull() { final TSSubQuery sub1 = getBaseQuery(); diff --git a/test/core/TestTsdbQueryQueries.java b/test/core/TestTsdbQueryQueries.java index 0f8c84551d..051b5157e9 100644 --- a/test/core/TestTsdbQueryQueries.java +++ b/test/core/TestTsdbQueryQueries.java @@ -14,6 +14,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.never; @@ -28,10 +30,12 @@ import java.util.Map; import net.opentsdb.storage.MockBase; +import net.opentsdb.storage.MockBase.MockScanner; import net.opentsdb.uid.NoSuchUniqueId; import net.opentsdb.utils.Config; import org.hbase.async.Bytes; +import org.hbase.async.FilterList; import org.hbase.async.Scanner; import org.junit.Before; import org.junit.Test; @@ -39,6 +43,8 @@ import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; +import com.stumbleupon.async.Deferred; + /** * An integration test class that makes sure our query path is up to snuff. * This class should have tests for different data point types, rates, @@ -1446,4 +1452,93 @@ public void runRegexpNoMatch() throws Exception { verify(tag_values, atLeast(1)).getNameAsync(TAGV_B_BYTES); assertEquals(0, dps.length); } + + @Test + public void filterExplicitTagsOK() throws Exception { + tsdb.getConfig().overrideConfig("tsd.query.enable_fuzzy", "true"); + storeLongTimeSeriesSeconds(true, false); + HashMap tags = new HashMap(1); + tags.put("host", "web01"); + query.setStartTime(1356998400); + query.setEndTime(1357041600); + query.setExplicitTags(true); + query.setTimeSeries("sys.cpu.user", tags, Aggregators.SUM, false); + + final DataPoints[] dps = query.run(); + + assertNotNull(dps); + assertEquals("sys.cpu.user", dps[0].metricName()); + assertTrue(dps[0].getAggregatedTags().isEmpty()); + assertNull(dps[0].getAnnotations()); + assertEquals("web01", dps[0].getTags().get("host")); + + int value = 1; + for (DataPoint dp : dps[0]) { + assertEquals(value, dp.longValue()); + value++; + } + assertEquals(300, dps[0].aggregatedSize()); + // assert fuzzy + for (final MockScanner scanner : storage.getScanners()) { + assertTrue(scanner.getFilter() instanceof FilterList); + } + } + + @Test + public void filterExplicitTagsGroupByOK() throws Exception { + tsdb.getConfig().overrideConfig("tsd.query.enable_fuzzy", "true"); + storeLongTimeSeriesSeconds(true, false); + HashMap tags = new HashMap(1); + tags.put("host", "*"); + query.setStartTime(1356998400); + query.setEndTime(1357041600); + query.setExplicitTags(true); + query.setTimeSeries("sys.cpu.user", tags, Aggregators.SUM, false); + + final DataPoints[] dps = query.run(); + + assertNotNull(dps); + assertEquals("sys.cpu.user", dps[0].metricName()); + assertTrue(dps[0].getAggregatedTags().isEmpty()); + assertNull(dps[0].getAnnotations()); + assertEquals("web01", dps[0].getTags().get("host")); + + int value = 1; + for (DataPoint dp : dps[0]) { + assertEquals(value, dp.longValue()); + value++; + } + assertEquals(300, dps[0].aggregatedSize()); + // assert fuzzy + for (final MockScanner scanner : storage.getScanners()) { + assertTrue(scanner.getFilter() instanceof FilterList); + } + } + + @Test + public void filterExplicitTagsMissing() throws Exception { + tsdb.getConfig().overrideConfig("tsd.query.enable_fuzzy", "true"); + when(tag_names.getIdAsync("colo")) + .thenReturn(Deferred.fromResult(new byte[] { 0, 0, 0, 4 })); + when(tag_values.getIdAsync("lga")) + .thenReturn(Deferred.fromResult(new byte[] { 0, 0, 0, 4 })); + storeLongTimeSeriesSeconds(true, false); + HashMap tags = new HashMap(1); + tags.put("host", "web01"); + tags.put("colo", "lga"); + query.setStartTime(1356998400); + query.setEndTime(1357041600); + query.setExplicitTags(true); + query.setTimeSeries("sys.cpu.user", tags, Aggregators.SUM, false); + + final DataPoints[] dps = query.run(); + + assertNotNull(dps); + assertEquals(0, dps.length); + // assert fuzzy + for (final MockScanner scanner : storage.getScanners()) { + assertTrue(scanner.getFilter() instanceof FilterList); + } + } + } diff --git a/test/storage/MockBase.java b/test/storage/MockBase.java index 73099e38b5..0a99c24197 100644 --- a/test/storage/MockBase.java +++ b/test/storage/MockBase.java @@ -33,6 +33,7 @@ import javax.xml.bind.DatatypeConverter; +import net.opentsdb.core.Const; import net.opentsdb.core.TSDB; import net.opentsdb.utils.Pair; @@ -41,10 +42,13 @@ import org.hbase.async.Bytes.ByteMap; import org.hbase.async.AppendRequest; import org.hbase.async.DeleteRequest; +import org.hbase.async.FilterList; import org.hbase.async.GetRequest; import org.hbase.async.HBaseClient; +import org.hbase.async.KeyRegexpFilter; import org.hbase.async.KeyValue; import org.hbase.async.PutRequest; +import org.hbase.async.ScanFilter; import org.hbase.async.Scanner; import org.junit.Ignore; import org.mockito.invocation.InvocationOnMock; @@ -632,6 +636,11 @@ public Set getKeys(final byte[] table) { return unique_rows.keySet(); } + /** @return The set of scanners configured by the caller */ + public HashSet getScanners() { + return scanners; + } + /** * Return the mocked TSDB object to use for HBaseClient access * @return @@ -1332,20 +1341,22 @@ public Deferred answer(InvocationOnMock invocation) * The KeyRegexp can be set and it will run against the hex value of the * row key. In testing it seems to work nicely even with byte patterns. */ - private class MockScanner implements + public class MockScanner implements Answer>>> { + private final Scanner mock_scanner; private final byte[] table; private byte[] start = null; private byte[] stop = null; private HashSet scnr_qualifiers = null; private byte[] family = null; - private String regex = null; + private ScanFilter filter = null; private int max_num_rows = Scanner.DEFAULT_MAX_NUM_ROWS; private ByteMap>>>> cursors; private ByteMap>>> cf_rows; private byte[] last_row; + private String rex; // TEMP /** * Default ctor @@ -1353,6 +1364,7 @@ private class MockScanner implements * @param table The table (confirmed to exist) */ public MockScanner(final Scanner mock_scanner, final byte[] table) { + this.mock_scanner = mock_scanner; this.table = table; // capture the scanner fields when set @@ -1360,7 +1372,8 @@ public MockScanner(final Scanner mock_scanner, final byte[] table) { @Override public Object answer(InvocationOnMock invocation) throws Throwable { final Object[] args = invocation.getArguments(); - regex = (String)args[0]; + filter = new KeyRegexpFilter((String)args[0], Const.ASCII_CHARSET); + rex = (String)args[0]; return null; } }).when(mock_scanner).setKeyRegexp(anyString()); @@ -1369,11 +1382,21 @@ public Object answer(InvocationOnMock invocation) throws Throwable { @Override public Object answer(InvocationOnMock invocation) throws Throwable { final Object[] args = invocation.getArguments(); - regex = (String)args[0]; + filter = new KeyRegexpFilter((String)args[0], (Charset)args[1]); + rex = (String)args[0]; return null; } }).when(mock_scanner).setKeyRegexp(anyString(), (Charset)any()); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + final Object[] args = invocation.getArguments(); + filter = (ScanFilter)args[0]; + return null; + } + }).when(mock_scanner).setFilter(any(ScanFilter.class)); + doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { @@ -1424,6 +1447,13 @@ public Object answer(InvocationOnMock invocation) throws Throwable { } }).when(mock_scanner).setQualifiers((byte[][])any()); + doAnswer(new Answer() { + @Override + public byte[] answer(InvocationOnMock invocation) throws Throwable { + return start; + } + }).when(mock_scanner).getCurrentKey(); + when(mock_scanner.nextRows()).thenAnswer(this); } @@ -1470,12 +1500,41 @@ public Deferred>> answer( return Deferred.fromResult(null); } + // TODO - fuzzy filter support + // TODO - fix the regex comparator Pattern pattern = null; - if (regex != null && !regex.isEmpty()) { - try { - pattern = Pattern.compile(regex); - } catch (PatternSyntaxException e) { - e.printStackTrace(); + if (rex != null) { + if (!rex.isEmpty()) { + pattern = Pattern.compile(rex); + } + } else if (filter != null) { + KeyRegexpFilter regex_filter = null; + + if (filter instanceof KeyRegexpFilter) { + regex_filter = (KeyRegexpFilter)filter; + } else if (filter instanceof FilterList) { + final List filters = + Whitebox.getInternalState(filter, "filters"); + for (final ScanFilter f : filters) { + if (f instanceof KeyRegexpFilter) { + regex_filter = (KeyRegexpFilter)f; + } + } + } + + if (regex_filter != null) { + try { + final String regexp = new String( + (byte[])Whitebox.getInternalState(regex_filter, "regexp"), + Charset.forName(new String( + (byte[])Whitebox.getInternalState(regex_filter, "charset")))); + if (!regexp.isEmpty()) { + pattern = Pattern.compile(regexp); + } + } catch (PatternSyntaxException e) { + e.printStackTrace(); + return Deferred.fromError(e); + } } } @@ -1624,6 +1683,16 @@ private void advance() { } } } + + /** @return The scanner for this mock */ + public Scanner getScanner() { + return mock_scanner; + } + + /** @return The filter for this mock */ + public ScanFilter getFilter() { + return filter; + } } /** diff --git a/test/tsd/TestQueryRpc.java b/test/tsd/TestQueryRpc.java index 666e3ac399..0741efb487 100644 --- a/test/tsd/TestQueryRpc.java +++ b/test/tsd/TestQueryRpc.java @@ -307,6 +307,55 @@ public void parseQueryMTypeWEmptyFilterBrackets() throws Exception { assertEquals(0, sub.getFilters().size()); } + @Test + public void parseQueryMTypeWExplicit() throws Exception { + HttpQuery query = NettyMocks.getQuery(tsdb, + "/api/query?start=1h-ago&m=sum:explicit_tags:sys.cpu.0{host=web01}"); + TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query, expressions); + TSSubQuery sub = tsq.getQueries().get(0); + assertNotNull(sub.getTags()); + assertEquals("literal_or(web01)", sub.getTags().get("host")); + assertTrue(sub.getExplicitTags()); + } + + @Test + public void parseQueryMTypeWExplicitAndRate() throws Exception { + HttpQuery query = NettyMocks.getQuery(tsdb, + "/api/query?start=1h-ago&m=sum:explicit_tags:rate:sys.cpu.0{host=web01}"); + TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query, expressions); + TSSubQuery sub = tsq.getQueries().get(0); + assertNotNull(sub.getTags()); + assertEquals("literal_or(web01)", sub.getTags().get("host")); + assertTrue(sub.getRate()); + assertTrue(sub.getExplicitTags()); + } + + @Test + public void parseQueryMTypeWExplicitAndRateAndDS() throws Exception { + HttpQuery query = NettyMocks.getQuery(tsdb, + "/api/query?start=1h-ago&m=sum:explicit_tags:rate:1m-sum:sys.cpu.0{host=web01}"); + TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query, expressions); + TSSubQuery sub = tsq.getQueries().get(0); + assertNotNull(sub.getTags()); + assertEquals("literal_or(web01)", sub.getTags().get("host")); + assertTrue(sub.getRate()); + assertTrue(sub.getExplicitTags()); + assertEquals("1m-sum", sub.getDownsample()); + } + + @Test + public void parseQueryMTypeWExplicitAndDSAndRate() throws Exception { + HttpQuery query = NettyMocks.getQuery(tsdb, + "/api/query?start=1h-ago&m=sum:explicit_tags:1m-sum:rate:sys.cpu.0{host=web01}"); + TSQuery tsq = (TSQuery) parseQuery.invoke(rpc, tsdb, query, expressions); + TSSubQuery sub = tsq.getQueries().get(0); + assertNotNull(sub.getTags()); + assertEquals("literal_or(web01)", sub.getTags().get("host")); + assertTrue(sub.getRate()); + assertTrue(sub.getExplicitTags()); + assertEquals("1m-sum", sub.getDownsample()); + } + @Test public void parseQueryTSUIDType() throws Exception { HttpQuery query = NettyMocks.getQuery(tsdb, From 47d94b8d2528ad7cf405b75faeadc993e81cb292 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sun, 28 Feb 2016 11:18:20 -0800 Subject: [PATCH 129/233] Add the NONE aggregator that prevents aggregation without having to specify a group by for every tag combination. Also helps with #713 Signed-off-by: Chris Larsen --- src/core/Aggregators.java | 34 +++++++++++++++++ src/core/Downsampler.java | 4 ++ src/core/DownsamplingSpecification.java | 8 ++++ src/core/TsdbQuery.java | 21 +++++++++++ test/core/TestDownsamplingSpecification.java | 5 +++ test/core/TestTSSubQuery.java | 7 ++++ test/core/TestTsdbQueryDownsample.java | 39 ++++++++++++++++++++ test/core/TestTsdbQueryQueries.java | 35 ++++++++++++++++++ 8 files changed, 153 insertions(+) diff --git a/src/core/Aggregators.java b/src/core/Aggregators.java index ce18e6e560..05eabf4e45 100644 --- a/src/core/Aggregators.java +++ b/src/core/Aggregators.java @@ -53,6 +53,9 @@ public enum Interpolation { public static final Aggregator AVG = new Avg( Interpolation.LERP, "avg"); + /** Aggregator that skips aggregation/interpolation and/or downsampling. */ + public static final Aggregator NONE = new None(Interpolation.ZIM, "raw"); + /** Return the product of two time series * @since 2.3 */ public static final Aggregator MULTIPLY = new Multiply( @@ -144,6 +147,7 @@ public enum Interpolation { aggregators.put("min", MIN); aggregators.put("max", MAX); aggregators.put("avg", AVG); + aggregators.put("none", NONE); aggregators.put("mult", MULTIPLY); aggregators.put("dev", DEV); aggregators.put("count", COUNT); @@ -319,6 +323,36 @@ public double runDouble(final Doubles values) { } + /** + * An aggregator that isn't meant for aggregation. Paradoxical!! + * Really it's used as a flag to indicate that, during sorting and iteration, + * that the pipeline should not perform any aggregation and should emit + * raw time series. + */ + private static final class None extends Aggregator { + public None(final Interpolation method, final String name) { + super(method, name); + } + + @Override + public long runLong(final Longs values) { + final long v = values.nextLongValue(); + if (values.hasNextValue()) { + throw new IllegalDataException("More than one value in aggregator " + values); + } + return v; + } + + @Override + public double runDouble(final Doubles values) { + final double v = values.nextDoubleValue(); + if (values.hasNextValue()) { + throw new IllegalDataException("More than one value in aggregator " + values); + } + return v; + } + } + private static final class Multiply extends Aggregator { public Multiply(final Interpolation method, final String name) { diff --git a/src/core/Downsampler.java b/src/core/Downsampler.java index d4d56ff51d..2b57a92c96 100644 --- a/src/core/Downsampler.java +++ b/src/core/Downsampler.java @@ -40,6 +40,10 @@ public class Downsampler implements SeekableView, DataPoint { final Aggregator downsampler) { this.values_in_interval = new ValuesInInterval(source, interval_ms); this.downsampler = downsampler; + if (downsampler == Aggregators.NONE) { + throw new IllegalArgumentException("cannot use the NONE " + + "aggregator for downsampling"); + } } // ------------------ // diff --git a/src/core/DownsamplingSpecification.java b/src/core/DownsamplingSpecification.java index 3f590d20f0..020638b7c4 100644 --- a/src/core/DownsamplingSpecification.java +++ b/src/core/DownsamplingSpecification.java @@ -71,6 +71,10 @@ public DownsamplingSpecification(final long interval, if (null == fill_policy) { throw new IllegalArgumentException("fill policy cannot be null"); } + if (function == Aggregators.NONE) { + throw new IllegalArgumentException("cannot use the NONE " + + "aggregator for downsampling"); + } this.interval = interval; this.function = function; @@ -115,6 +119,10 @@ public DownsamplingSpecification(final String specification) { throw new IllegalArgumentException("No such downsampling function: " + parts[1]); } + if (function == Aggregators.NONE) { + throw new IllegalArgumentException("cannot use the NONE " + + "aggregator for downsampling"); + } // FILL POLICY. if (3 == parts.length) { diff --git a/src/core/TsdbQuery.java b/src/core/TsdbQuery.java index 8ea2f48509..a5a1450506 100644 --- a/src/core/TsdbQuery.java +++ b/src/core/TsdbQuery.java @@ -427,6 +427,10 @@ public void downsample(final long interval, final Aggregator downsampler, */ @Override public void downsample(final long interval, final Aggregator downsampler) { + if (downsampler == Aggregators.NONE) { + throw new IllegalArgumentException("cannot use the NONE " + + "aggregator for downsampling"); + } downsample(interval, downsampler, FillPolicy.NONE); } @@ -850,6 +854,23 @@ public DataPoints[] call(final TreeMap spans) throws Exception { } return NO_RESULT; } + + // The raw aggregator skips group bys and ignores downsampling + if (aggregator == Aggregators.NONE) { + final SpanGroup[] groups = new SpanGroup[spans.size()]; + int i = 0; + for (final Span span : spans.values()) { + final SpanGroup group = new SpanGroup(tsdb, getScanStartTimeSeconds(), + getScanEndTimeSeconds(), + null, rate, rate_options, aggregator, + sample_interval_ms, downsampler, query_index, + fill_policy); + group.add(span); + groups[i++] = group; + } + return groups; + } + if (group_bys == null) { // We haven't been asked to find groups, so let's put all the spans // together in the same group. diff --git a/test/core/TestDownsamplingSpecification.java b/test/core/TestDownsamplingSpecification.java index a149a08c34..a85cb90f1a 100644 --- a/test/core/TestDownsamplingSpecification.java +++ b/test/core/TestDownsamplingSpecification.java @@ -65,5 +65,10 @@ public void testBadFunction() { public void testBadFillPolicy() { new DownsamplingSpecification("10m-avg-max"); } + + @Test (expected = IllegalArgumentException.class) + public void testNoneAgg() { + new DownsamplingSpecification("1m-none-lerp"); + } } diff --git a/test/core/TestTSSubQuery.java b/test/core/TestTSSubQuery.java index 92cd5661a8..a86ed8bb8f 100644 --- a/test/core/TestTSSubQuery.java +++ b/test/core/TestTSSubQuery.java @@ -225,6 +225,13 @@ public void validateWithFilterAndGroupByFilterSameTag() { assertEquals(300000, sub.downsampleInterval()); } + @Test (expected = IllegalArgumentException.class) + public void validateWithDownsampleNone() { + TSSubQuery sub = getMetricForValidate(); + sub.setDownsample("1m-none"); + sub.validateAndSetQuery(); + } + // NOTE: Each of the hash and equals tests should make sure that we the code // doesn't change after validation. diff --git a/test/core/TestTsdbQueryDownsample.java b/test/core/TestTsdbQueryDownsample.java index b213a73466..4e3b881232 100644 --- a/test/core/TestTsdbQueryDownsample.java +++ b/test/core/TestTsdbQueryDownsample.java @@ -28,7 +28,9 @@ import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.reflect.Whitebox; +import com.google.common.collect.Lists; import com.google.common.math.DoubleMath; /** @@ -497,6 +499,43 @@ public void runFloatSingleTSDownsampleAndRateAndCount() throws Exception { assertEquals(150, dps[0].size()); } + @Test (expected = IllegalArgumentException.class) + public void runLongSingleTSDownsampleNone() throws Exception { + storeLongTimeSeriesSeconds(true, false); + HashMap tags = new HashMap(1); + tags.put("host", "web01"); + query.setStartTime(1356998400); + query.setEndTime(1357041600); + query.downsample(60000, Aggregators.NONE); + query.setTimeSeries("sys.cpu.user", tags, Aggregators.SUM, false); + } + + @Test (expected = RuntimeException.class) + public void runLongSingleTSDownsampleNoneSnuckIn() throws Exception { + storeLongTimeSeriesSeconds(true, false); + final TSQuery ts_query = new TSQuery(); + ts_query.setStart("1356998400"); + ts_query.setEnd("1357041600"); + + final HashMap tags = new HashMap(1); + tags.put("host", "web01"); + final TSSubQuery sub = new TSSubQuery(); + sub.setTags(tags); + sub.setMetric("sys.cpu.user"); + sub.setAggregator("sum"); + sub.setDownsample("1m-sum"); + + ts_query.setQueries(Lists.newArrayList(sub)); + ts_query.validateAndSetQuery(); + query.configureFromQuery(ts_query, 0); + Whitebox.setInternalState(query, "downsampler", Aggregators.NONE); + + final DataPoints[] dps = query.run(); + for (DataPoint dp : dps[0]) { + dp.timestamp(); + } + } + /** * A helper interface to be used by the filling-test code. */ diff --git a/test/core/TestTsdbQueryQueries.java b/test/core/TestTsdbQueryQueries.java index 051b5157e9..4deaea7daf 100644 --- a/test/core/TestTsdbQueryQueries.java +++ b/test/core/TestTsdbQueryQueries.java @@ -302,6 +302,41 @@ public void runFloatTwoAggSum() throws Exception { assertEquals(300, dps[0].size()); } + @Test + public void runFloatTwoAggNoneAgg() throws Exception { + storeFloatTimeSeriesSeconds(true, false); + + tags.clear(); + query.setStartTime(1356998400); + query.setEndTime(1357041600); + query.setTimeSeries(METRIC_STRING, tags, Aggregators.NONE, false); + + final DataPoints[] dps = query.run(); + assertMeta(dps, 0, false); + assertMeta(dps, 1, false); + assertEquals(2, dps.length); + + double value = 1.25D; + long timestamp = 1356998430000L; + for (DataPoint dp : dps[0]) { + assertEquals(value, dp.doubleValue(), 0.0001); + assertEquals(timestamp, dp.timestamp()); + value += 0.25D; + timestamp += 30000; + } + assertEquals(300, dps[0].size()); + + value = 75D; + timestamp = 1356998430000L; + for (DataPoint dp : dps[1]) { + assertEquals(value, dp.doubleValue(), 0.0001); + assertEquals(timestamp, dp.timestamp()); + value -= 0.25d; + timestamp += 30000; + } + assertEquals(300, dps[1].size()); + } + @Test public void runFloatTwoAggSumMs() throws Exception { storeFloatTimeSeriesMs(); From 6e6e45399f2d896be7551696c7b56c664c0d8362 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sun, 28 Feb 2016 13:40:05 -0800 Subject: [PATCH 130/233] Allow for downsampling all values between the query start and end times into a single value Signed-off-by: Chris Larsen --- src/core/AggregationIterator.java | 49 +++++ src/core/Downsampler.java | 132 +++++++++--- src/core/DownsamplingSpecification.java | 21 +- src/core/FillingDownsampler.java | 62 ++++-- src/core/Span.java | 25 +++ src/core/SpanGroup.java | 67 +++++-- src/core/TSSubQuery.java | 12 +- src/core/TsdbQuery.java | 70 +++---- test/core/TestDownsampler.java | 193 +++++++++++++++++- test/core/TestDownsamplingSpecification.java | 2 +- test/core/TestFillingDownsampler.java | 201 +++++++++++++++---- test/core/TestTsdbQueryDownsample.java | 100 ++++++++- 12 files changed, 789 insertions(+), 145 deletions(-) diff --git a/src/core/AggregationIterator.java b/src/core/AggregationIterator.java index a53636b751..03bc93b8cf 100644 --- a/src/core/AggregationIterator.java +++ b/src/core/AggregationIterator.java @@ -277,6 +277,55 @@ public static AggregationIterator create(final List spans, method, rate); } + /** + * Creates a new iterator for a {@link SpanGroup}. + * @param spans Spans in a group. + * @param start_time Any data point strictly before this timestamp will be + * ignored. + * @param end_time Any data point strictly after this timestamp will be + * ignored. + * @param aggregator The aggregation function to use. + * @param method Interpolation method to use when aggregating time series + * @param downsampler The downsampling specifier to use (cannot be null) + * @param query_start Start of the actual query + * @param query_end End of the actual query + * @param rate If {@code true}, the rate of the series will be used instead + * of the actual values. + * @param rate_options Specifies the optional additional rate calculation + * options. + * @return an AggregationIterator + * @since 2.3 + */ + public static AggregationIterator create(final List spans, + final long start_time, + final long end_time, + final Aggregator aggregator, + final Interpolation method, + final DownsamplingSpecification downsampler, + final long query_start, + final long query_end, + final boolean rate, + final RateOptions rate_options) { + final int size = spans.size(); + final SeekableView[] iterators = new SeekableView[size]; + for (int i = 0; i < size; i++) { + SeekableView it; + if (downsampler == null || + downsampler == DownsamplingSpecification.NO_DOWNSAMPLER) { + it = spans.get(i).spanIterator(); + } else { + it = spans.get(i).downsampler(start_time, end_time, downsampler, + query_start, query_end); + } + if (rate) { + it = new RateSpan(it, rate_options); + } + iterators[i] = it; + } + return new AggregationIterator(iterators, start_time, end_time, aggregator, + method, rate); + } + /** * Creates an aggregation iterator for a group of data point iterators. * @param iterators An array of Seekable views of spans in a group. Ignored diff --git a/src/core/Downsampler.java b/src/core/Downsampler.java index 2b57a92c96..06bd421582 100644 --- a/src/core/Downsampler.java +++ b/src/core/Downsampler.java @@ -19,31 +19,79 @@ */ public class Downsampler implements SeekableView, DataPoint { - /** Function to use for downsampling. */ - protected final Aggregator downsampler; + /** The downsampling specification when provided */ + protected final DownsamplingSpecification specification; + + /** The start timestamp of the actual query for use with "all" */ + protected final long query_start; + + /** The end timestamp of the actual query for use with "all" */ + protected final long query_end; + + /** The data source */ + protected final SeekableView source; + /** Iterator to iterate the values of the current interval. */ protected final ValuesInInterval values_in_interval; + /** Last normalized timestamp */ protected long timestamp; + /** Last value as a double */ protected double value; + /** Whether or not to merge all DPs in the source into one vaalue */ + protected final boolean run_all; + /** * Ctor. * @param source The iterator to access the underlying data. * @param interval_ms The interval in milli seconds wanted between each data * point. * @param downsampler The downsampling function to use. + * @deprecated as of 2.3 */ Downsampler(final SeekableView source, final long interval_ms, final Aggregator downsampler) { - this.values_in_interval = new ValuesInInterval(source, interval_ms); - this.downsampler = downsampler; + this.source = source; + values_in_interval = new ValuesInInterval(); if (downsampler == Aggregators.NONE) { throw new IllegalArgumentException("cannot use the NONE " + "aggregator for downsampling"); } + specification = new DownsamplingSpecification(interval_ms, downsampler, + DownsamplingSpecification.DEFAULT_FILL_POLICY); + query_start = 0; + query_end = 0; + run_all = false; + } + + /** + * Ctor. + * @param source The iterator to access the underlying data. + * @param specification The downsampling spec to use + * @param query_start The start timestamp of the actual query for use with "all" + * @param query_end The end timestamp of the actual query for use with "all" + * @since 2.3 + */ + Downsampler(final SeekableView source, + final DownsamplingSpecification specification, + final long query_start, + final long query_end + ) { + this.source = source; + this.specification = specification; + values_in_interval = new ValuesInInterval(); + this.query_start = query_start; + this.query_end = query_end; + + final String s = specification.getStringInterval(); + if (s != null && s.toLowerCase().contains("all")) { + run_all = true; + } else { + run_all = false; + } } // ------------------ // @@ -61,7 +109,7 @@ public boolean hasNext() { @Override public DataPoint next() { if (hasNext()) { - value = downsampler.runDouble(values_in_interval); + value = specification.getFunction().runDouble(values_in_interval); timestamp = values_in_interval.getIntervalTimestamp(); values_in_interval.moveToNextInterval(); return this; @@ -89,6 +137,9 @@ public void seek(final long timestamp) { @Override public long timestamp() { + if (run_all) { + return query_start; + } return timestamp; } @@ -116,8 +167,10 @@ public double toDouble() { public String toString() { final StringBuilder buf = new StringBuilder(); buf.append("Downsampler: ") - .append("interval_ms=").append(values_in_interval.interval_ms) - .append(", downsampler=").append(downsampler) + .append(", downsampler=").append(specification) + .append(", queryStart=").append(query_start) + .append(", queryEnd=").append(query_end) + .append(", runAll=").append(run_all) .append(", current data=(timestamp=").append(timestamp) .append(", value=").append(value) .append("), values_in_interval=").append(values_in_interval); @@ -125,16 +178,14 @@ public String toString() { } /** Iterates source values for an interval. */ - protected static class ValuesInInterval implements Aggregator.Doubles { + protected class ValuesInInterval implements Aggregator.Doubles { - /** The iterator of original source values. */ - private final SeekableView source; - /** The sampling interval in milliseconds. */ - protected final long interval_ms; /** The end of the current interval. */ private long timestamp_end_interval = Long.MIN_VALUE; + /** True if the last value was successfully extracted from the source. */ private boolean has_next_value_from_source = false; + /** The last data point extracted from the source. */ private DataPoint next_dp = null; @@ -143,13 +194,11 @@ protected static class ValuesInInterval implements Aggregator.Doubles { /** * Constructor. - * @param source The iterator to access the underlying data. - * @param interval_ms Downsampling interval. */ - ValuesInInterval(final SeekableView source, final long interval_ms) { - this.source = source; - this.interval_ms = interval_ms; - this.timestamp_end_interval = interval_ms; + protected ValuesInInterval() { + if (run_all) { + timestamp_end_interval = query_end; + } } /** Initializes to iterate intervals. */ @@ -160,7 +209,9 @@ protected void initializeIfNotDone() { if (!initialized) { initialized = true; moveToNextValue(); - resetEndOfInterval(); + if (!run_all) { + resetEndOfInterval(); + } } } @@ -168,7 +219,25 @@ protected void initializeIfNotDone() { private void moveToNextValue() { if (source.hasNext()) { has_next_value_from_source = true; - next_dp = source.next(); + // filter out dps that don't match start and end for run_alls + if (run_all) { + while (source.hasNext()) { + next_dp = source.next(); + if (next_dp.timestamp() < query_start) { + next_dp = null; + continue; + } + if (next_dp.timestamp() >= query_end) { + has_next_value_from_source = false; + } + break; + } + if (next_dp == null) { + has_next_value_from_source = false; + } + } else { + next_dp = source.next(); + } } else { has_next_value_from_source = false; } @@ -179,10 +248,10 @@ private void moveToNextValue() { * the next value read from source. It is the first value of the next * interval. */ private void resetEndOfInterval() { - if (has_next_value_from_source) { + if (has_next_value_from_source && !run_all) { // Sets the end of the interval of the timestamp. timestamp_end_interval = alignTimestamp(next_dp.timestamp()) + - interval_ms; + specification.getInterval(); } } @@ -198,7 +267,11 @@ void seekInterval(final long timestamp) { // rounds up the seeking timestamp to the smallest timestamp that is // a multiple of the interval and is greater than or equal to the given // timestamp.. - source.seek(alignTimestamp(timestamp + interval_ms - 1)); + if (run_all) { + source.seek(timestamp); + } else { + source.seek(alignTimestamp(timestamp + specification.getInterval() - 1)); + } initialized = false; } @@ -207,12 +280,17 @@ protected long getIntervalTimestamp() { // NOTE: It is well-known practice taking the start time of // a downsample interval as a representative timestamp of it. It also // provides the correct context for seek. - return alignTimestamp(timestamp_end_interval - interval_ms); + if (run_all) { + return timestamp_end_interval; + } else { + return alignTimestamp(timestamp_end_interval - + specification.getInterval()); + } } /** Returns timestamp aligned by interval. */ protected long alignTimestamp(final long timestamp) { - return timestamp - (timestamp % interval_ms); + return timestamp - (timestamp % specification.getInterval()); } // ---------------------- // @@ -222,6 +300,9 @@ protected long alignTimestamp(final long timestamp) { @Override public boolean hasNextValue() { initializeIfNotDone(); + if (run_all) { + return has_next_value_from_source; + } return has_next_value_from_source && next_dp.timestamp() < timestamp_end_interval; } @@ -241,7 +322,6 @@ public double nextDoubleValue() { public String toString() { final StringBuilder buf = new StringBuilder(); buf.append("ValuesInInterval: ") - .append("interval_ms=").append(interval_ms) .append(", timestamp_end_interval=").append(timestamp_end_interval) .append(", has_next_value_from_source=") .append(has_next_value_from_source); diff --git a/src/core/DownsamplingSpecification.java b/src/core/DownsamplingSpecification.java index 020638b7c4..5abe810029 100644 --- a/src/core/DownsamplingSpecification.java +++ b/src/core/DownsamplingSpecification.java @@ -37,6 +37,9 @@ public final class DownsamplingSpecification { // Parsed downsample interval. private final long interval; + + //The string interval, e.g. 1h, 30d, etc + private final String string_interval; // Parsed downsampler function. private final Aggregator function; @@ -51,6 +54,7 @@ private DownsamplingSpecification() { interval = NO_INTERVAL; function = NO_FUNCTION; fill_policy = DEFAULT_FILL_POLICY; + string_interval = null; } /** @@ -59,6 +63,7 @@ private DownsamplingSpecification() { * @param function The downsampling function. * @param fill_policy The policy specifying how to deal with missing data. * @throws IllegalArgumentException if any argument is invalid. + * @deprecated since 2.3 */ public DownsamplingSpecification(final long interval, final Aggregator function, final FillPolicy fill_policy) { @@ -79,6 +84,7 @@ public DownsamplingSpecification(final long interval, this.interval = interval; this.function = function; this.fill_policy = fill_policy; + string_interval = null; } /** @@ -110,7 +116,13 @@ public DownsamplingSpecification(final String specification) { // INTERVAL. // This will throw if interval is invalid. - interval = DateTime.parseDuration(parts[0]); + if (parts[0].contains("all")) { + interval = NO_INTERVAL; + string_interval = parts[0]; + } else { + interval = DateTime.parseDuration(parts[0]); + string_interval = parts[0]; + } // FUNCTION. try { @@ -153,6 +165,12 @@ public long getInterval() { return interval; } + /** @return The string interval from the user (without the 'c' if given) + * @since 2.3 */ + public String getStringInterval() { + return string_interval; + } + /** * Get the downsampling function. * @return the downsampling function. @@ -175,6 +193,7 @@ public String toString() { .add("interval", getInterval()) .add("function", getFunction()) .add("fillPolicy", getFillPolicy()) + .add("stringInterval", string_interval) .toString(); } } diff --git a/src/core/FillingDownsampler.java b/src/core/FillingDownsampler.java index 0e4d77ad10..bcba99f61a 100644 --- a/src/core/FillingDownsampler.java +++ b/src/core/FillingDownsampler.java @@ -26,9 +26,6 @@ public class FillingDownsampler extends Downsampler { /** Track when the downsampled data should end. */ protected long end_timestamp; - /** Downsampling fill policy. */ - protected final FillPolicy fill_policy; - /** * Create a new nulling downsampler. * @param source The iterator to access the underlying data. @@ -40,24 +37,50 @@ public class FillingDownsampler extends Downsampler { * @param fill_policy Policy specifying whether to interpolate or to fill * missing intervals with special values. * @throws IllegalArgumentException if fill_policy is interpolation. + * @deprecated as of 2.3 */ FillingDownsampler(final SeekableView source, final long start_time, final long end_time, final long interval_ms, final Aggregator downsampler, final FillPolicy fill_policy) { + this(source, start_time, end_time, + new DownsamplingSpecification(interval_ms, downsampler, fill_policy) + , 0, 0); + } + + /** + * Create a new filling downsampler. + * @param source The iterator to access the underlying data. + * @param start_time The time in milliseconds at which the data begins. + * @param end_time The time in milliseconds at which the data ends. + * @param specification The downsampling spec to use + * @param query_start The start timestamp of the actual query for use with "all" + * @param query_end The end timestamp of the actual query for use with "all" + * @throws IllegalArgumentException if fill_policy is interpolation. + * @since 2.3 + */ + FillingDownsampler(final SeekableView source, final long start_time, + final long end_time, final DownsamplingSpecification specification, + final long query_start, final long end_start) { // Lean on the superclass implementation. - super(source, interval_ms, downsampler); + super(source, specification, query_start, end_start); // Ensure we aren't given a bogus fill policy. - if (FillPolicy.NONE == fill_policy) { + if (FillPolicy.NONE == specification.getFillPolicy()) { throw new IllegalArgumentException("Cannot instantiate this class with" + " linear-interpolation fill policy"); } - this.fill_policy = fill_policy; - + // Use the values-in-interval object to align the timestamps at which we // expect data to arrive for the first and last intervals. - this.timestamp = values_in_interval.alignTimestamp(start_time); - this.end_timestamp = values_in_interval.alignTimestamp(end_time); + if (run_all) { + timestamp = start_time; + end_timestamp = end_time; + } else { + // Use the values-in-interval object to align the timestamps at which we + // expect data to arrive for the first and last intervals. + timestamp = values_in_interval.alignTimestamp(start_time); + end_timestamp = values_in_interval.alignTimestamp(end_time); + } } /** @@ -72,6 +95,9 @@ public boolean hasNext() { // No matter the state of the values-in-interval object, if our current // timestamp hasn't reached the end of the requested overall interval, then // we still have iterating to do. + if (run_all) { + return values_in_interval.hasNextValue(); + } return timestamp < end_timestamp; } @@ -92,26 +118,27 @@ public DataPoint next() { // Skip any leading data outside the query bounds. long actual = values_in_interval.getIntervalTimestamp(); - while (values_in_interval.hasNextValue() && actual < timestamp) { + while (!run_all && values_in_interval.hasNextValue() + && actual < timestamp) { // The actual timestamp precedes our expected, so there's data in the // values-in-interval object that we wish to ignore. - downsampler.runDouble(values_in_interval); + specification.getFunction().runDouble(values_in_interval); values_in_interval.moveToNextInterval(); actual = values_in_interval.getIntervalTimestamp(); } // Check whether the timestamp of the calculation interval matches what // we expect. - if (actual == timestamp) { + if (run_all || actual == timestamp) { // The calculated interval timestamp matches what we expect, so we can // do normal processing. - value = downsampler.runDouble(values_in_interval); + value = specification.getFunction().runDouble(values_in_interval); values_in_interval.moveToNextInterval(); } else { // Our expected timestamp precedes the actual, so the interval is // missing. We will use a special value, based on the fill policy, to // represent this case. - switch (fill_policy) { + switch (specification.getFillPolicy()) { case NOT_A_NUMBER: case NULL: value = Double.NaN; @@ -127,7 +154,7 @@ public DataPoint next() { } // Advance the expected timestamp to the next interval. - timestamp += values_in_interval.interval_ms; + timestamp += specification.getInterval(); // This object also represents the data. return this; @@ -140,7 +167,10 @@ public DataPoint next() { @Override public long timestamp() { - return timestamp - values_in_interval.interval_ms; + if (run_all) { + return query_start; + } + return timestamp - specification.getInterval(); } } diff --git a/src/core/Span.java b/src/core/Span.java index c9f31ba77f..15338956ae 100644 --- a/src/core/Span.java +++ b/src/core/Span.java @@ -485,6 +485,31 @@ Downsampler downsampler(final long start_time, interval_ms, downsampler, fill_policy); } } + + /** + * @param start_time The time in milliseconds at which the data begins. + * @param end_time The time in milliseconds at which the data ends. + * @param downsampler The downsampling specification to use + * @param query_start Start of the actual query + * @param query_end End of the actual query + * @return A new downsampler. + * @since 2.3 + */ + Downsampler downsampler(final long start_time, + final long end_time, + final DownsamplingSpecification downsampler, + final long query_start, + final long query_end) { + if (downsampler == null) { + return null; + } + if (FillPolicy.NONE == downsampler.getFillPolicy()) { + return new Downsampler(spanIterator(), downsampler, + query_start, query_end); + } + return new FillingDownsampler(spanIterator(), start_time, end_time, + downsampler, query_start, query_end); + } public int getQueryIndex() { throw new UnsupportedOperationException("Not mapped to a query"); diff --git a/src/core/SpanGroup.java b/src/core/SpanGroup.java index 17625699ef..90133dcf51 100644 --- a/src/core/SpanGroup.java +++ b/src/core/SpanGroup.java @@ -91,21 +91,18 @@ final class SpanGroup implements DataPoints { /** Aggregator to use to aggregate data points from different Spans. */ private final Aggregator aggregator; - /** - * Downsampling function to use, if any (can be {@code null}). - * If this is non-null, {@code sample_interval} must be strictly positive. - */ - private final Aggregator downsampler; - - /** Minimum time interval (in seconds) wanted between each data point. */ - private final long sample_interval; + /** Downsampling specification to use, if any (can be {@code null}). */ + private DownsamplingSpecification downsampler; + + /** Start timestamp of the query for filtering */ + private final long query_start; + + /** End timestamp of the query for filtering */ + private final long query_end; /** Index of the query in the TSQuery class */ private final int query_index; - /** Downsampling fill policy. */ - private final FillPolicy fill_policy; - /** The TSDB to which we belong, used for resolution */ private final TSDB tsdb; @@ -192,6 +189,43 @@ final class SpanGroup implements DataPoints { final Aggregator aggregator, final long interval, final Aggregator downsampler, final int query_index, final FillPolicy fill_policy) { + this(tsdb, start_time, end_time, spans, rate, rate_options, aggregator, + downsampler != null ? + new DownsamplingSpecification(interval, downsampler, fill_policy) : + null, + query_index, 0, 0); + } + + /** + * Ctor. + * @param tsdb The TSDB we belong to. + * @param start_time Any data point strictly before this timestamp will be + * ignored. + * @param end_time Any data point strictly after this timestamp will be + * ignored. + * @param spans A sequence of initial {@link Spans} to add to this group. + * Ignored if {@code null}. Additional spans can be added with {@link #add}. + * @param rate If {@code true}, the rate of the series will be used instead + * of the actual values. + * @param rate_options Specifies the optional additional rate calculation options. + * @param aggregator The aggregation function to use. + * @param downsampler The specification to use for downsampling, may be null. + * @param query_start Start of the actual query + * @param query_end End of the actual query + * @param query_index index of the original query + * @since 2.3 + */ + SpanGroup(final TSDB tsdb, + final long start_time, + final long end_time, + final Iterable spans, + final boolean rate, + final RateOptions rate_options, + final Aggregator aggregator, + final DownsamplingSpecification downsampler, + final long query_start, + final long query_end, + final int query_index) { annotations = new ArrayList(); this.start_time = (start_time & Const.SECOND_MASK) == 0 ? start_time * 1000 : start_time; @@ -206,9 +240,9 @@ final class SpanGroup implements DataPoints { this.rate_options = rate_options; this.aggregator = aggregator; this.downsampler = downsampler; - this.sample_interval = interval; + this.query_start = query_start; + this.query_end = query_end; this.query_index = query_index; - this.fill_policy = fill_policy; this.tsdb = tsdb; } @@ -452,8 +486,8 @@ public int aggregatedSize() { public SeekableView iterator() { return AggregationIterator.create(spans, start_time, end_time, aggregator, aggregator.interpolationMethod(), - downsampler, sample_interval, - rate, rate_options, fill_policy); + downsampler, query_start, query_end, + rate, rate_options); } /** @@ -509,7 +543,8 @@ private String toStringSharedAttributes() { + ", rate=" + rate + ", aggregator=" + aggregator + ", downsampler=" + downsampler - + ", sample_interval=" + sample_interval + + ", query_start=" + query_start + + ", query_end" + query_end + ')'; } diff --git a/src/core/TSSubQuery.java b/src/core/TSSubQuery.java index bff500a75f..578a32b89f 100644 --- a/src/core/TSSubQuery.java +++ b/src/core/TSSubQuery.java @@ -211,16 +211,24 @@ public Aggregator aggregator() { return this.agg; } - /** @return the parsed downsampler aggregation function */ + /** @return the parsed downsampler aggregation function + * @deprecated use {@link #downsamplingSpecification()} instead */ public Aggregator downsampler() { return downsample_specifier.getFunction(); } - /** @return the parsed downsample interval in seconds */ + /** @return the parsed downsample interval in seconds + * @deprecated use {@link #downsamplingSpecification()} instead */ public long downsampleInterval() { return downsample_specifier.getInterval(); } + /** @return The downsampling specification for more options + * @since 2.3 */ + public DownsamplingSpecification downsamplingSpecification() { + return downsample_specifier; + } + /** * @return the downsampling fill policy * @since 2.2 diff --git a/src/core/TsdbQuery.java b/src/core/TsdbQuery.java index a5a1450506..105cecf8c2 100644 --- a/src/core/TsdbQuery.java +++ b/src/core/TsdbQuery.java @@ -119,17 +119,8 @@ final class TsdbQuery implements Query { /** Aggregator function to use. */ private Aggregator aggregator; - /** - * Downsampling function to use, if any (can be {@code null}). - * If this is non-null, {@code sample_interval_ms} must be strictly positive. - */ - private Aggregator downsampler; - - /** Minimum time interval (in milliseconds) wanted between each data point. */ - private long sample_interval_ms; - - /** Downsampling fill policy. */ - private FillPolicy fill_policy; + /** Downsampling specification to use, if any (can be {@code null}). */ + private DownsamplingSpecification downsampler; /** Optional list of TSUIDs to fetch and aggregate instead of a metric */ private List tsuids; @@ -151,8 +142,6 @@ public TsdbQuery(final TSDB tsdb) { this.tsdb = tsdb; enable_fuzzy_filter = tsdb.getConfig() .getBoolean("tsd.query.enable_fuzzy_filter"); - // By default, we should interpolate. - fill_policy = DownsamplingSpecification.DEFAULT_FILL_POLICY; } /** @@ -322,6 +311,7 @@ public void setExplicitTags(final boolean explicit_tags) { this.explicit_tags = explicit_tags; } + @Override public Deferred configureFromQuery(final TSQuery query, final int index) { if (query.getQueries() == null || query.getQueries().isEmpty()) { @@ -345,9 +335,7 @@ public Deferred configureFromQuery(final TSQuery query, if (rate_options == null) { rate_options = new RateOptions(); } - downsampler = sub_query.downsampler(); - sample_interval_ms = sub_query.downsampleInterval(); - fill_policy = sub_query.fillPolicy(); + downsampler = sub_query.downsamplingSpecification(); filters = sub_query.getFilters(); explicit_tags = sub_query.getExplicitTags(); @@ -408,14 +396,8 @@ public Object call(final byte[] uid) throws Exception { @Override public void downsample(final long interval, final Aggregator downsampler, final FillPolicy fill_policy) { - if (downsampler == null) { - throw new NullPointerException("downsampler"); - } else if (interval <= 0) { - throw new IllegalArgumentException("interval not > 0: " + interval); - } - this.downsampler = downsampler; - this.sample_interval_ms = interval; - this.fill_policy = fill_policy; + this.downsampler = new DownsamplingSpecification( + interval, downsampler,fill_policy); } /** @@ -860,11 +842,18 @@ public DataPoints[] call(final TreeMap spans) throws Exception { final SpanGroup[] groups = new SpanGroup[spans.size()]; int i = 0; for (final Span span : spans.values()) { - final SpanGroup group = new SpanGroup(tsdb, getScanStartTimeSeconds(), + final SpanGroup group = new SpanGroup( + tsdb, + getScanStartTimeSeconds(), getScanEndTimeSeconds(), - null, rate, rate_options, aggregator, - sample_interval_ms, downsampler, query_index, - fill_policy); + null, + rate, + rate_options, + aggregator, + downsampler, + getStartTime(), + getEndTime(), + query_index); group.add(span); groups[i++] = group; } @@ -880,8 +869,10 @@ public DataPoints[] call(final TreeMap spans) throws Exception { spans.values(), rate, rate_options, aggregator, - sample_interval_ms, downsampler, - query_index, fill_policy); + downsampler, + getStartTime(), + getEndTime(), + query_index); if (query_stats != null) { query_stats.addStat(query_index, QueryStat.GROUP_BY_TIME, 0); } @@ -928,8 +919,10 @@ public DataPoints[] call(final TreeMap spans) throws Exception { thegroup = new SpanGroup(tsdb, getScanStartTimeSeconds(), getScanEndTimeSeconds(), null, rate, rate_options, aggregator, - sample_interval_ms, downsampler, query_index, - fill_policy); + downsampler, + getStartTime(), + getEndTime(), + query_index); // Copy the array because we're going to keep `group' and overwrite // its contents. So we want the collection to have an immutable copy. final byte[] group_copy = new byte[group.length]; @@ -1010,9 +1003,10 @@ private long getScanStartTimeSeconds() { // First, we align the start timestamp to its representative value for the // interval in which it appears, if downsampling. long interval_aligned_ts = start; - if (0L != sample_interval_ms) { + if (downsampler != null && downsampler.getInterval() > 0) { // Downsampling enabled. - final long interval_offset = (1000L * start) % sample_interval_ms; + // TODO - calendar interval + final long interval_offset = (1000L * start) % downsampler.getInterval(); interval_aligned_ts -= interval_offset / 1000L; } @@ -1036,7 +1030,7 @@ private long getScanEndTimeSeconds() { } // The calculation depends on whether we're downsampling. - if (0L != sample_interval_ms) { + if (downsampler != null && downsampler.getInterval() > 0) { // Downsampling enabled. // // First, we align the end timestamp to its representative value for the @@ -1049,9 +1043,9 @@ private long getScanEndTimeSeconds() { // skip forward an entire extra interval. // // This can be accomplished by simply not testing for zero offset. - final long interval_offset = (1000L * end) % sample_interval_ms; + final long interval_offset = (1000L * end) % downsampler.getInterval(); final long interval_aligned_ts = end + - (sample_interval_ms - interval_offset) / 1000L; + (downsampler.getInterval() - interval_offset) / 1000L; // Then, if we're now aligned on a timespan boundary, then we need no // further adjustment: we are guaranteed to have always moved the end time @@ -1217,7 +1211,7 @@ static long getScanEndTimeSeconds(final TsdbQuery query) { /** @return the downsampling interval for unit tests. */ static long getDownsampleIntervalMs(final TsdbQuery query) { - return query.sample_interval_ms; + return query.downsampler.getInterval(); } static byte[] getMetric(final TsdbQuery query) { diff --git a/test/core/TestDownsampler.java b/test/core/TestDownsampler.java index 32e94c8d2a..97b54b02de 100644 --- a/test/core/TestDownsampler.java +++ b/test/core/TestDownsampler.java @@ -12,7 +12,6 @@ // see . package net.opentsdb.core; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -29,7 +28,6 @@ import org.junit.Before; import org.junit.Test; - /** Tests {@link Downsampler}. */ public class TestDownsampler { @@ -57,6 +55,7 @@ public class TestDownsampler { private SeekableView source; private Downsampler downsampler; + private DownsamplingSpecification specification; @Before public void before() { @@ -65,7 +64,8 @@ public void before() { @Test public void testDownsampler() { - downsampler = new Downsampler(source, THOUSAND_SEC_INTERVAL, AVG); + specification = new DownsamplingSpecification("1000s-avg"); + downsampler = new Downsampler(source, specification, 0, 0); verify(source, never()).next(); List values = Lists.newArrayList(); List timestamps_in_millis = Lists.newArrayList(); @@ -90,7 +90,7 @@ public void testDownsampler() { } @Test - public void testDownsampler_10seconds() { + public void testDownsamplerDeprecated_10seconds() { source = spy(SeekableViewsForTest.fromArray(new DataPoint[] { MutableDataPoint.ofDoubleValue(BASE_TIME + 5000L * 0, 1), MutableDataPoint.ofDoubleValue(BASE_TIME + 5000L * 1, 2), @@ -131,7 +131,49 @@ public void testDownsampler_10seconds() { } @Test - public void testDownsampler_15seconds() { + public void testDownsampler_10seconds() { + source = spy(SeekableViewsForTest.fromArray(new DataPoint[] { + MutableDataPoint.ofDoubleValue(BASE_TIME + 5000L * 0, 1), + MutableDataPoint.ofDoubleValue(BASE_TIME + 5000L * 1, 2), + MutableDataPoint.ofDoubleValue(BASE_TIME + 5000L * 2, 4), + MutableDataPoint.ofDoubleValue(BASE_TIME + 5000L * 3, 8), + MutableDataPoint.ofDoubleValue(BASE_TIME + 5000L * 4, 16), + MutableDataPoint.ofDoubleValue(BASE_TIME + 5000L * 5, 32), + MutableDataPoint.ofDoubleValue(BASE_TIME + 5000L * 6, 64), + MutableDataPoint.ofDoubleValue(BASE_TIME + 5000L * 7, 128), + MutableDataPoint.ofDoubleValue(BASE_TIME + 5000L * 8, 256), + MutableDataPoint.ofDoubleValue(BASE_TIME + 5000L * 9, 512), + MutableDataPoint.ofDoubleValue(BASE_TIME + 5000L * 10, 1024) + })); + specification = new DownsamplingSpecification("10s-sum"); + downsampler = new Downsampler(source, specification, 0, 0); + verify(source, never()).next(); + List values = Lists.newArrayList(); + List timestamps_in_millis = Lists.newArrayList(); + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + values.add(dp.doubleValue()); + timestamps_in_millis.add(dp.timestamp()); + } + + assertEquals(6, values.size()); + assertEquals(3, values.get(0), 0.0000001); + assertEquals(BASE_TIME + 00000L, timestamps_in_millis.get(0).longValue()); + assertEquals(12, values.get(1), 0.0000001); + assertEquals(BASE_TIME + 10000L, timestamps_in_millis.get(1).longValue()); + assertEquals(48, values.get(2), 0.0000001); + assertEquals(BASE_TIME + 20000L, timestamps_in_millis.get(2).longValue()); + assertEquals(192, values.get(3), 0.0000001); + assertEquals(BASE_TIME + 30000L, timestamps_in_millis.get(3).longValue()); + assertEquals(768, values.get(4), 0.0000001); + assertEquals(BASE_TIME + 40000L, timestamps_in_millis.get(4).longValue()); + assertEquals(1024, values.get(5), 0.0000001); + assertEquals(BASE_TIME + 50000L, timestamps_in_millis.get(5).longValue()); + } + + @Test + public void testDownsamplerDeprecated_15seconds() { source = spy(SeekableViewsForTest.fromArray(new DataPoint[] { MutableDataPoint.ofLongValue(BASE_TIME + 5000L, 1), MutableDataPoint.ofLongValue(BASE_TIME + 15000L, 2), @@ -162,6 +204,147 @@ public void testDownsampler_15seconds() { assertEquals(BASE_TIME + 45000L, timestamps_in_millis.get(3).longValue()); } + @Test + public void testDownsampler_15seconds() { + source = spy(SeekableViewsForTest.fromArray(new DataPoint[] { + MutableDataPoint.ofLongValue(BASE_TIME + 5000L, 1), + MutableDataPoint.ofLongValue(BASE_TIME + 15000L, 2), + MutableDataPoint.ofLongValue(BASE_TIME + 25000L, 4), + MutableDataPoint.ofLongValue(BASE_TIME + 35000L, 8), + MutableDataPoint.ofLongValue(BASE_TIME + 45000L, 16), + MutableDataPoint.ofLongValue(BASE_TIME + 55000L, 32) + })); + specification = new DownsamplingSpecification("15s-sum"); + downsampler = new Downsampler(source, specification, 0, 0); + verify(source, never()).next(); + List values = Lists.newArrayList(); + List timestamps_in_millis = Lists.newArrayList(); + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + values.add(dp.doubleValue()); + timestamps_in_millis.add(dp.timestamp()); + } + + assertEquals(4, values.size()); + assertEquals(1, values.get(0), 0.0000001); + assertEquals(BASE_TIME + 00000L, timestamps_in_millis.get(0).longValue()); + assertEquals(6, values.get(1), 0.0000001); + assertEquals(BASE_TIME + 15000L, timestamps_in_millis.get(1).longValue()); + assertEquals(8, values.get(2), 0.0000001); + assertEquals(BASE_TIME + 30000L, timestamps_in_millis.get(2).longValue()); + assertEquals(48, values.get(3), 0.0000001); + assertEquals(BASE_TIME + 45000L, timestamps_in_millis.get(3).longValue()); + } + + @Test + public void testDownsampler_allFullRange() { + source = spy(SeekableViewsForTest.fromArray(new DataPoint[] { + MutableDataPoint.ofLongValue(BASE_TIME + 5000L, 1), + MutableDataPoint.ofLongValue(BASE_TIME + 15000L, 2), + MutableDataPoint.ofLongValue(BASE_TIME + 25000L, 4), + MutableDataPoint.ofLongValue(BASE_TIME + 35000L, 8), + MutableDataPoint.ofLongValue(BASE_TIME + 45000L, 16), + MutableDataPoint.ofLongValue(BASE_TIME + 55000L, 32) + })); + specification = new DownsamplingSpecification("0all-sum"); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); + System.out.println(downsampler); + verify(source, never()).next(); + List values = Lists.newArrayList(); + List timestamps_in_millis = Lists.newArrayList(); + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + values.add(dp.doubleValue()); + timestamps_in_millis.add(dp.timestamp()); + } + + assertEquals(1, values.size()); + assertEquals(63, values.get(0), 0.0000001); + assertEquals(0L, timestamps_in_millis.get(0).longValue()); + } + + @Test + public void testDownsampler_allFilterOnQuery() { + source = spy(SeekableViewsForTest.fromArray(new DataPoint[] { + MutableDataPoint.ofLongValue(BASE_TIME + 5000L, 1), + MutableDataPoint.ofLongValue(BASE_TIME + 15000L, 2), + MutableDataPoint.ofLongValue(BASE_TIME + 25000L, 4), + MutableDataPoint.ofLongValue(BASE_TIME + 35000L, 8), + MutableDataPoint.ofLongValue(BASE_TIME + 45000L, 16), + MutableDataPoint.ofLongValue(BASE_TIME + 55000L, 32) + })); + specification = new DownsamplingSpecification("0all-sum"); + downsampler = new Downsampler(source, specification, + BASE_TIME + 15000L, BASE_TIME + 45000L); + verify(source, never()).next(); + List values = Lists.newArrayList(); + List timestamps_in_millis = Lists.newArrayList(); + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + values.add(dp.doubleValue()); + timestamps_in_millis.add(dp.timestamp()); + } + + assertEquals(1, values.size()); + assertEquals(14, values.get(0), 0.0000001); + assertEquals(BASE_TIME + 15000L, timestamps_in_millis.get(0).longValue()); + } + + @Test + public void testDownsampler_allFilterOnQueryOutOfRangeEarly() { + source = spy(SeekableViewsForTest.fromArray(new DataPoint[] { + MutableDataPoint.ofLongValue(BASE_TIME + 5000L, 1), + MutableDataPoint.ofLongValue(BASE_TIME + 15000L, 2), + MutableDataPoint.ofLongValue(BASE_TIME + 25000L, 4), + MutableDataPoint.ofLongValue(BASE_TIME + 35000L, 8), + MutableDataPoint.ofLongValue(BASE_TIME + 45000L, 16), + MutableDataPoint.ofLongValue(BASE_TIME + 55000L, 32) + })); + specification = new DownsamplingSpecification("0all-sum"); + downsampler = new Downsampler(source, specification, + BASE_TIME + 65000L, BASE_TIME + 75000L); + verify(source, never()).next(); + List values = Lists.newArrayList(); + List timestamps_in_millis = Lists.newArrayList(); + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + values.add(dp.doubleValue()); + timestamps_in_millis.add(dp.timestamp()); + } + + assertEquals(0, values.size()); + } + + @Test + public void testDownsampler_allFilterOnQueryOutOfRangeLate() { + source = spy(SeekableViewsForTest.fromArray(new DataPoint[] { + MutableDataPoint.ofLongValue(BASE_TIME + 5000L, 1), + MutableDataPoint.ofLongValue(BASE_TIME + 15000L, 2), + MutableDataPoint.ofLongValue(BASE_TIME + 25000L, 4), + MutableDataPoint.ofLongValue(BASE_TIME + 35000L, 8), + MutableDataPoint.ofLongValue(BASE_TIME + 45000L, 16), + MutableDataPoint.ofLongValue(BASE_TIME + 55000L, 32) + })); + specification = new DownsamplingSpecification("0all-sum"); + downsampler = new Downsampler(source, specification, + BASE_TIME - 15000L, BASE_TIME - 5000L); + verify(source, never()).next(); + List values = Lists.newArrayList(); + List timestamps_in_millis = Lists.newArrayList(); + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + values.add(dp.doubleValue()); + timestamps_in_millis.add(dp.timestamp()); + } + + assertEquals(0, values.size()); + } + @Test(expected = UnsupportedOperationException.class) public void testRemove() { new Downsampler(source, THOUSAND_SEC_INTERVAL, AVG).remove(); diff --git a/test/core/TestDownsamplingSpecification.java b/test/core/TestDownsamplingSpecification.java index a85cb90f1a..0fc51f0368 100644 --- a/test/core/TestDownsamplingSpecification.java +++ b/test/core/TestDownsamplingSpecification.java @@ -44,7 +44,7 @@ public void testStringCtor() { @Test public void testToString() { assertEquals("DownsamplingSpecification{interval=4532019, function=zimsum, " - + "fillPolicy=NOT_A_NUMBER}", + + "fillPolicy=NOT_A_NUMBER, stringInterval=null}", new DownsamplingSpecification( 4532019L, Aggregators.ZIMSUM, diff --git a/test/core/TestFillingDownsampler.java b/test/core/TestFillingDownsampler.java index 70dd698aca..e762a8d33b 100644 --- a/test/core/TestFillingDownsampler.java +++ b/test/core/TestFillingDownsampler.java @@ -16,16 +16,14 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; /** Tests {@link FillingDownsampler}. */ public class TestFillingDownsampler { - private static final Aggregator SUM = Aggregators.get("sum"); - - private static final FillPolicy NAN = FillPolicy.fromString("nan"); - private static final FillPolicy ZERO = FillPolicy.fromString("zero"); - + private static final long BASE_TIME = 1356998400000L; + + private DownsamplingSpecification specification; + /** Data with gaps: before, during, and after. */ @Test public void testNaNMissingInterval() { @@ -43,18 +41,20 @@ public void testNaNMissingInterval() { MutableDataPoint.ofDoubleValue(baseTime + 25L * 27L, 1.), }); + specification = new DownsamplingSpecification("100ms-sum-nan"); final Downsampler downsampler = new FillingDownsampler(source, baseTime, - baseTime + 36 * 25L, 100L, SUM, NAN); - - step(downsampler, Double.NaN); - step(downsampler, 3.); - step(downsampler, Double.NaN); - step(downsampler, 2.); - step(downsampler, Double.NaN); - step(downsampler, Double.NaN); - step(downsampler, 4.); - step(downsampler, Double.NaN); - step(downsampler, Double.NaN); + baseTime + 36 * 25L, specification, 0, 0); + + long timestamp = baseTime; + step(downsampler, timestamp, Double.NaN); + step(downsampler, timestamp += 100, 3.); + step(downsampler, timestamp += 100, Double.NaN); + step(downsampler, timestamp += 100, 2.); + step(downsampler, timestamp += 100, Double.NaN); + step(downsampler, timestamp += 100, Double.NaN); + step(downsampler, timestamp += 100, 4.); + step(downsampler, timestamp += 100, Double.NaN); + step(downsampler, timestamp += 100, Double.NaN); assertFalse(downsampler.hasNext()); } @@ -73,19 +73,21 @@ public void testZeroMissingInterval() { MutableDataPoint.ofDoubleValue(baseTime + 25L * 26L, 1.), MutableDataPoint.ofDoubleValue(baseTime + 25L * 27L, 1.), }); - + + specification = new DownsamplingSpecification("100ms-sum-zero"); final Downsampler downsampler = new FillingDownsampler(source, baseTime, - baseTime + 36 * 25L, 100L, SUM, ZERO); - - step(downsampler, 0.); - step(downsampler, 3.); - step(downsampler, 0.); - step(downsampler, 2.); - step(downsampler, 0.); - step(downsampler, 0.); - step(downsampler, 4.); - step(downsampler, 0.); - step(downsampler, 0.); + baseTime + 36 * 25L, specification, 0, 0); + + long timestamp = baseTime; + step(downsampler, timestamp, 0.); + step(downsampler, timestamp += 100, 3.); + step(downsampler, timestamp += 100, 0.); + step(downsampler, timestamp += 100, 2.); + step(downsampler, timestamp += 100, 0.); + step(downsampler, timestamp += 100, 0.); + step(downsampler, timestamp += 100, 4.); + step(downsampler, timestamp += 100, 0.); + step(downsampler, timestamp += 100, 0.); assertFalse(downsampler.hasNext()); } @@ -109,12 +111,14 @@ public void testWithoutMissingIntervals() { MutableDataPoint.ofDoubleValue(baseTime + 25L * 11L, 1.), }); + specification = new DownsamplingSpecification("100ms-sum-nan"); final Downsampler downsampler = new FillingDownsampler(source, baseTime, - baseTime + 12L * 25L, 100L, SUM, NAN); + baseTime + 12L * 25L, specification, 0, 0); - step(downsampler, 42.); - step(downsampler, 26.); - step(downsampler, 10.); + long timestamp = baseTime; + step(downsampler, timestamp, 42.); + step(downsampler, timestamp += 100, 26.); + step(downsampler, timestamp += 100, 10.); assertFalse(downsampler.hasNext()); } @@ -139,20 +143,139 @@ public void testWithOutOfBoundsData() { MutableDataPoint.ofDoubleValue(baseTime + 60000L * 2L + 30384L, 37.), MutableDataPoint.ofDoubleValue(baseTime + 60000L * 4L + 1530L, 86.) }); + + specification = new DownsamplingSpecification("1m-sum-nan"); + final Downsampler downsampler = new FillingDownsampler(source, baseTime, + baseTime + 60000L * 2L, specification, 0, 0); + + long timestamp = 1425335880000L; + step(downsampler, timestamp, 30.); + step(downsampler, timestamp += 60000, 9.); + assertFalse(downsampler.hasNext()); + } + + @Test + public void testWithOutOfBoundsDataEarly() { + final long baseTime = 1425335895000L; + final SeekableView source = + SeekableViewsForTest.fromArray(new DataPoint[] { + MutableDataPoint.ofDoubleValue(baseTime - 60000L * 5L + 320L, 53.), + MutableDataPoint.ofDoubleValue(baseTime - 60000L * 2L + 8839L, 16.) + }); + specification = new DownsamplingSpecification("1m-sum-nan"); final Downsampler downsampler = new FillingDownsampler(source, baseTime, - baseTime + 60000L * 2L, 60000L, SUM, NAN); + baseTime + 60000L * 2L, specification, 0, 0); + + long timestamp = 1425335880000L; + step(downsampler, timestamp, Double.NaN); + step(downsampler, timestamp += 60000, Double.NaN); + assertFalse(downsampler.hasNext()); + } + + @Test + public void testWithOutOfBoundsDataLate() { + final long baseTime = 1425335895000L; + final SeekableView source = + SeekableViewsForTest.fromArray(new DataPoint[] { + MutableDataPoint.ofDoubleValue(baseTime + 60000L * 2L + 30384L, 37.), + MutableDataPoint.ofDoubleValue(baseTime + 60000L * 4L + 1530L, 86.) + }); - step(downsampler, 30.); - step(downsampler, 9.); + specification = new DownsamplingSpecification("1m-sum-nan"); + final Downsampler downsampler = new FillingDownsampler(source, baseTime, + baseTime + 60000L * 2L, specification, 0, 0); + + long timestamp = 1425335880000L; + step(downsampler, timestamp, Double.NaN); + step(downsampler, timestamp += 60000, Double.NaN); assertFalse(downsampler.hasNext()); } - private void step(final Downsampler downsampler, final double expected) { + @Test + public void testDownsampler_allFullRange() { + final SeekableView source = SeekableViewsForTest.fromArray(new DataPoint[] { + MutableDataPoint.ofLongValue(BASE_TIME + 5000L, 1), + MutableDataPoint.ofLongValue(BASE_TIME + 15000L, 2), + MutableDataPoint.ofLongValue(BASE_TIME + 25000L, 4), + MutableDataPoint.ofLongValue(BASE_TIME + 35000L, 8), + MutableDataPoint.ofLongValue(BASE_TIME + 45000L, 16), + MutableDataPoint.ofLongValue(BASE_TIME + 55000L, 32) + }); + + specification = new DownsamplingSpecification("0all-sum-nan"); + final Downsampler downsampler = new FillingDownsampler(source, + BASE_TIME + 5000L,BASE_TIME + 55000L, specification, 0, + Long.MAX_VALUE); + + step(downsampler, 0, 63); + assertFalse(downsampler.hasNext()); + } + + @Test + public void testDownsampler_allFilterOnQuery() { + final SeekableView source = SeekableViewsForTest.fromArray(new DataPoint[] { + MutableDataPoint.ofLongValue(BASE_TIME + 5000L, 1), + MutableDataPoint.ofLongValue(BASE_TIME + 15000L, 2), + MutableDataPoint.ofLongValue(BASE_TIME + 25000L, 4), + MutableDataPoint.ofLongValue(BASE_TIME + 35000L, 8), + MutableDataPoint.ofLongValue(BASE_TIME + 45000L, 16), + MutableDataPoint.ofLongValue(BASE_TIME + 55000L, 32) + }); + + specification = new DownsamplingSpecification("0all-sum-nan"); + final Downsampler downsampler = new FillingDownsampler(source, + BASE_TIME + 5000L,BASE_TIME + 55000L, specification, + BASE_TIME + 15000L, BASE_TIME + 45000L); + + step(downsampler, BASE_TIME + 15000L, 14); + assertFalse(downsampler.hasNext()); + } + + @Test + public void testDownsampler_allFilterOnQueryOutOfRangeEarly() { + final SeekableView source = SeekableViewsForTest.fromArray(new DataPoint[] { + MutableDataPoint.ofLongValue(BASE_TIME + 5000L, 1), + MutableDataPoint.ofLongValue(BASE_TIME + 15000L, 2), + MutableDataPoint.ofLongValue(BASE_TIME + 25000L, 4), + MutableDataPoint.ofLongValue(BASE_TIME + 35000L, 8), + MutableDataPoint.ofLongValue(BASE_TIME + 45000L, 16), + MutableDataPoint.ofLongValue(BASE_TIME + 55000L, 32) + }); + + specification = new DownsamplingSpecification("0all-sum-nan"); + final Downsampler downsampler = new FillingDownsampler(source, + BASE_TIME + 5000L,BASE_TIME + 55000L, specification, + BASE_TIME + 65000L, BASE_TIME + 75000L); + + assertFalse(downsampler.hasNext()); + } + + @Test + public void testDownsampler_allFilterOnQueryOutOfRangeLate() { + final SeekableView source = SeekableViewsForTest.fromArray(new DataPoint[] { + MutableDataPoint.ofLongValue(BASE_TIME + 5000L, 1), + MutableDataPoint.ofLongValue(BASE_TIME + 15000L, 2), + MutableDataPoint.ofLongValue(BASE_TIME + 25000L, 4), + MutableDataPoint.ofLongValue(BASE_TIME + 35000L, 8), + MutableDataPoint.ofLongValue(BASE_TIME + 45000L, 16), + MutableDataPoint.ofLongValue(BASE_TIME + 55000L, 32) + }); + + specification = new DownsamplingSpecification("0all-sum-nan"); + final Downsampler downsampler = new FillingDownsampler(source, + BASE_TIME + 5000L,BASE_TIME + 55000L, specification, + BASE_TIME - 15000L, BASE_TIME - 5000L); + + assertFalse(downsampler.hasNext()); + } + + private void step(final Downsampler downsampler, final long expected_timestamp, + final double expected_value) { assertTrue(downsampler.hasNext()); final DataPoint point = downsampler.next(); - assertNotNull(point); - assertEquals(expected, point.doubleValue(), 0.01); + assertEquals(expected_timestamp, point.timestamp()); + assertEquals(expected_value, point.doubleValue(), 0.01); } } diff --git a/test/core/TestTsdbQueryDownsample.java b/test/core/TestTsdbQueryDownsample.java index 4e3b881232..02bdce9469 100644 --- a/test/core/TestTsdbQueryDownsample.java +++ b/test/core/TestTsdbQueryDownsample.java @@ -124,7 +124,7 @@ public void downsampleMilliseconds() throws Exception { TsdbQuery.ForTesting.getScanEndTimeSeconds(query)); } - @Test (expected = NullPointerException.class) + @Test (expected = IllegalArgumentException.class) public void downsampleNullAgg() throws Exception { query.downsample(60, null); } @@ -461,6 +461,104 @@ public void runLongSingleTSDownsampleCount() throws Exception { assertEquals(151, dps[0].size()); } + @Test + public void runLongSingleTSDownsampleAll() throws Exception { + storeLongTimeSeriesSeconds(true, false); + final TSQuery ts_query = new TSQuery(); + ts_query.setStart("1356998400"); + ts_query.setEnd("1357041600"); + + final HashMap tags = new HashMap(1); + tags.put("host", "web01"); + final TSSubQuery sub = new TSSubQuery(); + sub.setTags(tags); + sub.setMetric("sys.cpu.user"); + sub.setAggregator("sum"); + sub.setDownsample("0all-sum"); + + ts_query.setQueries(Lists.newArrayList(sub)); + ts_query.validateAndSetQuery(); + query.configureFromQuery(ts_query, 0); + + final DataPoints[] dps = query.run(); + assertMeta(dps, 0, false); + + for (DataPoint dp : dps[0]) { + // Downsampler outputs just doubles. + assertFalse(dp.isInteger()); + assertEquals(45150, dp.doubleValue(), 0.00001); + assertEquals(1356998400000L, dp.timestamp()); + } + // Out of 300 values, the first and the last intervals have one value each, + // and the 149 intervals in the middle have two values for each. + assertEquals(1, dps[0].size()); + } + + @Test + public void runLongSingleTSDownsampleAllSubSet() throws Exception { + storeLongTimeSeriesSeconds(true, false); + final TSQuery ts_query = new TSQuery(); + ts_query.setStart("1356998500"); + ts_query.setEnd("1356998600"); + + final HashMap tags = new HashMap(1); + tags.put("host", "web01"); + final TSSubQuery sub = new TSSubQuery(); + sub.setTags(tags); + sub.setMetric("sys.cpu.user"); + sub.setAggregator("sum"); + sub.setDownsample("0all-sum"); + + ts_query.setQueries(Lists.newArrayList(sub)); + ts_query.validateAndSetQuery(); + query.configureFromQuery(ts_query, 0); + + final DataPoints[] dps = query.run(); + assertMeta(dps, 0, false); + + for (DataPoint dp : dps[0]) { + // Downsampler outputs just doubles. + assertFalse(dp.isInteger()); + assertEquals(15, dp.doubleValue(), 0.00001); + assertEquals(1356998500000L, dp.timestamp()); + } + // Out of 300 values, the first and the last intervals have one value each, + // and the 149 intervals in the middle have two values for each. + assertEquals(1, dps[0].size()); + } + + @Test + public void runLongSingleTSDownsampleAllNoEnd() throws Exception { + storeLongTimeSeriesSeconds(true, false); + final TSQuery ts_query = new TSQuery(); + ts_query.setStart("1356998400"); + + final HashMap tags = new HashMap(1); + tags.put("host", "web01"); + final TSSubQuery sub = new TSSubQuery(); + sub.setTags(tags); + sub.setMetric("sys.cpu.user"); + sub.setAggregator("sum"); + sub.setDownsample("0all-sum"); + + ts_query.setQueries(Lists.newArrayList(sub)); + ts_query.validateAndSetQuery(); + query.configureFromQuery(ts_query, 0); + + final DataPoints[] dps = query.run(); + assertMeta(dps, 0, false); + + for (DataPoint dp : dps[0]) { + // Downsampler outputs just doubles. + assertFalse(dp.isInteger()); + assertEquals(45150, dp.doubleValue(), 0.00001); + assertEquals(1356998400000L, dp.timestamp()); + } + // Out of 300 values, the first and the last intervals have one value each, + // and the 149 intervals in the middle have two values for each. + assertEquals(1, dps[0].size()); + } + // this could happen. @Test public void runFloatSingleTSDownsampleAndRateAndCount() throws Exception { From 43eb419aa381f651eb1f16928d6c916d3680c297 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sun, 28 Feb 2016 16:06:49 -0800 Subject: [PATCH 131/233] Bump the asyncbigtable version to include filters that TSD needs Signed-off-by: Chris Larsen --- ...gtable-0.2.1-20160228.235952-3-jar-with-dependencies.jar.md5 | 1 + third_party/asyncbigtable/include.mk | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 third_party/asyncbigtable/asyncbigtable-0.2.1-20160228.235952-3-jar-with-dependencies.jar.md5 diff --git a/third_party/asyncbigtable/asyncbigtable-0.2.1-20160228.235952-3-jar-with-dependencies.jar.md5 b/third_party/asyncbigtable/asyncbigtable-0.2.1-20160228.235952-3-jar-with-dependencies.jar.md5 new file mode 100644 index 0000000000..78c394f1d3 --- /dev/null +++ b/third_party/asyncbigtable/asyncbigtable-0.2.1-20160228.235952-3-jar-with-dependencies.jar.md5 @@ -0,0 +1 @@ +512cc4c7ba345a11aa8d6662d03bb3ed diff --git a/third_party/asyncbigtable/include.mk b/third_party/asyncbigtable/include.mk index 9e903a49f7..8549f1451c 100644 --- a/third_party/asyncbigtable/include.mk +++ b/third_party/asyncbigtable/include.mk @@ -13,7 +13,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see . -ASYNCBIGTABLE_VERSION := 0.2.1-20151029.214823-2 +ASYNCBIGTABLE_VERSION := 0.2.1-20160228.235952-3 ASYNCBIGTABLE := third_party/asyncbigtable/asyncbigtable-$(ASYNCBIGTABLE_VERSION)-jar-with-dependencies.jar ASYNCBIGTABLE_BASE_URL := https://oss.sonatype.org/content/repositories/snapshots/com/pythian/opentsdb/asyncbigtable/0.2.1-SNAPSHOT/ From e5b8be4d6eecf0eda949ffb895a2aba1225cc9d6 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sun, 28 Feb 2016 16:46:33 -0800 Subject: [PATCH 132/233] Bump the asynccassandra version with filters needed for the TSD Signed-off-by: Chris Larsen --- ...sandra-0.0.1-20160229.001338-4-jar-with-dependencies.jar.md5 | 1 + third_party/asynccassandra/include.mk | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 third_party/asynccassandra/asynccassandra-0.0.1-20160229.001338-4-jar-with-dependencies.jar.md5 diff --git a/third_party/asynccassandra/asynccassandra-0.0.1-20160229.001338-4-jar-with-dependencies.jar.md5 b/third_party/asynccassandra/asynccassandra-0.0.1-20160229.001338-4-jar-with-dependencies.jar.md5 new file mode 100644 index 0000000000..cbd7d36bb3 --- /dev/null +++ b/third_party/asynccassandra/asynccassandra-0.0.1-20160229.001338-4-jar-with-dependencies.jar.md5 @@ -0,0 +1 @@ +cb857f54223905d744fafa475d82623f \ No newline at end of file diff --git a/third_party/asynccassandra/include.mk b/third_party/asynccassandra/include.mk index 7eb8f99e96..f98aab5050 100644 --- a/third_party/asynccassandra/include.mk +++ b/third_party/asynccassandra/include.mk @@ -13,7 +13,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see . -ASYNCCASSANDRA_VERSION := 0.0.1-20151104.191228-3 +ASYNCCASSANDRA_VERSION := 0.0.1-20160229.001338-4 ASYNCCASSANDRA := third_party/asynccassandra/asynccassandra-$(ASYNCCASSANDRA_VERSION)-jar-with-dependencies.jar ASYNCCASSANDRA_BASE_URL := https://oss.sonatype.org/content/repositories/snapshots/net/opentsdb/asynccassandra/0.0.1-SNAPSHOT/ From 07ab5f29cd79297bbde8fb73d8298a84f8820913 Mon Sep 17 00:00:00 2001 From: dominosly Date: Fri, 4 Mar 2016 16:54:05 -0800 Subject: [PATCH 133/233] Adding support latest java versions 1.8.0u65 to 1.8.0u74 and their corresponding ALPN versions for BigTable build. --- third_party/alpn-boot/alpn-boot-8.1.6.v20151105.jar.md5 | 1 + third_party/alpn-boot/alpn-boot-8.1.7.v20160121.jar.md5 | 1 + third_party/alpn-boot/include.mk | 7 ++++++- 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 third_party/alpn-boot/alpn-boot-8.1.6.v20151105.jar.md5 create mode 100644 third_party/alpn-boot/alpn-boot-8.1.7.v20160121.jar.md5 diff --git a/third_party/alpn-boot/alpn-boot-8.1.6.v20151105.jar.md5 b/third_party/alpn-boot/alpn-boot-8.1.6.v20151105.jar.md5 new file mode 100644 index 0000000000..209c34b148 --- /dev/null +++ b/third_party/alpn-boot/alpn-boot-8.1.6.v20151105.jar.md5 @@ -0,0 +1 @@ +0f7bbc8e3da3948082c4d3a510d6fe43 \ No newline at end of file diff --git a/third_party/alpn-boot/alpn-boot-8.1.7.v20160121.jar.md5 b/third_party/alpn-boot/alpn-boot-8.1.7.v20160121.jar.md5 new file mode 100644 index 0000000000..a70c6c1356 --- /dev/null +++ b/third_party/alpn-boot/alpn-boot-8.1.7.v20160121.jar.md5 @@ -0,0 +1 @@ +4af7a18a9b4549a1796b182dabca9062 \ No newline at end of file diff --git a/third_party/alpn-boot/include.mk b/third_party/alpn-boot/include.mk index c4f94eb448..13cd19dfa9 100644 --- a/third_party/alpn-boot/include.mk +++ b/third_party/alpn-boot/include.mk @@ -14,6 +14,7 @@ # along with this library. If not, see . # ALPN_BOOT_VERSION := 7.1.3.v20150130 + ALPN_BOOT_VERSION = $(shell version= ;\ if [[ "@JAVA@" ]]; then \ version=$$("@JAVA@" -version 2>&1 | awk -F '"' '/version/ {print $$2}'); \ @@ -42,8 +43,12 @@ ALPN_BOOT_VERSION = $(shell version= ;\ echo "8.1.3.v20150130"; \ elif [[ $$sub < 60 ]]; then \ echo "8.1.4.v20150727"; \ - else \ + elif [[ $$sub < 65 ]]; then \ echo "8.1.5.v20150921"; \ + elif [[ $$sub < 71 ]]; then \ + echo "8.1.6.v20151105"; \ + else \ + echo "8.1.7.v20160121"; \ fi \ else \ echo "Unsupported major Java version: $$major"; \ From 507f80a007f1aa8a40b1c6de9dbaeb03d451e368 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sun, 6 Mar 2016 11:30:33 -0800 Subject: [PATCH 134/233] Add the explicitTags to the expressions endpoint Signed-off-by: Chris Larsen --- src/query/pojo/Filter.java | 25 ++++++++++++++++++++++--- src/tsd/QueryExecutor.java | 1 + test/query/pojo/TestFilter.java | 8 +++++--- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/query/pojo/Filter.java b/src/query/pojo/Filter.java index 902422da1f..c1eeea2818 100644 --- a/src/query/pojo/Filter.java +++ b/src/query/pojo/Filter.java @@ -34,6 +34,10 @@ public class Filter extends Validatable { /** The list of filters in the filter set */ private List tags; + /** Whether or not to only fetch series with exactly the same tag keys as + * in the filter list. */ + private boolean explicit_tags; + /** * Default ctor * @param builder The builder to pull values from @@ -41,6 +45,7 @@ public class Filter extends Validatable { private Filter(Builder builder) { this.id = builder.id; this.tags = builder.tags; + this.explicit_tags = builder.explicitTags; } /** @return the id of the filter set to use in a metric query */ @@ -53,6 +58,12 @@ public List getTags() { return tags; } + /** @return Whether or not to only fetch series with exactly the same tag keys as + * in the filter list. */ + public boolean getExplicitTags() { + return explicit_tags; + } + /** @return A new builder for the filter */ public static Builder Builder() { return new Builder(); @@ -78,12 +89,13 @@ public boolean equals(final Object o) { final Filter filter = (Filter) o; return Objects.equal(id, filter.id) - && Objects.equal(tags, filter.tags); + && Objects.equal(tags, filter.tags) + && Objects.equal(explicit_tags, filter.explicit_tags); } @Override public int hashCode() { - return Objects.hashCode(id, tags); + return Objects.hashCode(id, tags, explicit_tags); } /** @@ -96,7 +108,9 @@ public static final class Builder { private String id; @JsonProperty private List tags; - + @JsonProperty + private boolean explicitTags; + public Builder setId(String id) { Query.validateId(id); this.id = id; @@ -108,6 +122,11 @@ public Builder setTags(List tags) { return this; } + public Builder setExplicitTags(boolean explicit_tags) { + this.explicitTags = explicit_tags; + return this; + } + public Filter build() { return new Filter(this); } diff --git a/src/tsd/QueryExecutor.java b/src/tsd/QueryExecutor.java index 90cdca0165..20e81bd76b 100644 --- a/src/tsd/QueryExecutor.java +++ b/src/tsd/QueryExecutor.java @@ -170,6 +170,7 @@ public QueryExecutor(final TSDB tsdb, final Query query) { sub.setFilters(filters.getTags()); sub.setAggregator( mq.getAggregator() != null ? mq.getAggregator() : timespan.getAggregator()); + sub.setExplicitTags(filters.getExplicitTags()); } } diff --git a/test/query/pojo/TestFilter.java b/test/query/pojo/TestFilter.java index e38088c09b..1d1f9ea0a5 100644 --- a/test/query/pojo/TestFilter.java +++ b/test/query/pojo/TestFilter.java @@ -41,14 +41,15 @@ public void validationBadId() throws Exception { @Test public void deserialize() throws Exception { String json = "{\"id\":\"f1\",\"tags\":[{\"tagk\":\"host\"," - + "\"filter\":\"*\",\"type\":\"iwildcard\",\"groupBy\":false}]}"; + + "\"filter\":\"*\",\"type\":\"iwildcard\",\"groupBy\":false}]," + + "\"explicitTags\":\"true\"}"; TagVFilter tag = new TagVFilter.Builder().setFilter("*").setGroupBy( false) .setTagk("host").setType("iwildcard").build(); Filter expectedFilter = Filter.Builder().setId("f1") - .setTags(Arrays.asList(tag)).build(); + .setTags(Arrays.asList(tag)).setExplicitTags(true).build(); Filter filter = JSON.parseToObject(json, Filter.class); filter.validate(); @@ -61,12 +62,13 @@ public void serialize() throws Exception { .setTagk("host").setType("iwildcard").build(); Filter filter = Filter.Builder().setId("f1") - .setTags(Arrays.asList(tag)).build(); + .setTags(Arrays.asList(tag)).setExplicitTags(true).build(); String actual = JSON.serializeToString(filter); assertTrue(actual.contains("\"id\":\"f1\"")); assertTrue(actual.contains("\"tags\":[")); assertTrue(actual.contains("\"tagk\":\"host\"")); + assertTrue(actual.contains("\"explicitTags\":true")); } @Test From 5012f7da15bb872bae3c4fd6129b10d38c7837c4 Mon Sep 17 00:00:00 2001 From: Carlos Devoto Date: Sat, 12 Mar 2016 15:18:01 -0800 Subject: [PATCH 135/233] Align downsampling intervals to the Gregorian calendar. This feature supports the alignment of downsampling intervals to the Gregorian calendar based on four different time categories: - DAILY: The start time of each interval is computed as the start of the day in which the first data point occurs, based on a specified time zone (or the default JVM time zone, if no time zone has been specified). The end time of each interval is computed as the end of the day in which the first data point occurs. For instance, if the specified time zone is UTC, and the timestamp of the first data point is 2016-01-05T05:32:00Z, then start of the interval will be computed as 2016-01-05T00:00:00.000Z, while the end of the interval will be computed as 2016-01-05T23:59:59.999Z. - WEEKLY: The start time of each interval is computed as the start of the week in which the first data point occurs, based on a specified time zone (or the default JVM time zone, if no time zone has been specified). The end time of each interval is computed as the end of the week in which the first data point occurs. Weeks are considered to begin on Sundays (in the future, it might be a good idea to allow for variations based on a configuration setting). For instance, if the specified time zone is UTC, and the timestamp of the first data point is 2016-01-05T05:32:00Z, then start of the interval will be computed as 2016-01-03T00:00:00.000Z, while the end of the interval will be computed as 2016-01-09T23:59:59.999Z. - MONTHLY: The start time of each interval is computed as the start of the month in which the first data point occurs, based on a specified time zone (or the default JVM time zone, if no time zone has been specified). The end time of each interval is computed as the end of the month in which the first data point occurs. For instance, if the specified time zone is UTC, and the timestamp of the first data point is 2016-01-05T05:32:00Z, then start of the interval will be computed as 2016-01-01T00:00:00.000Z, while the end of the interval will be computed as 2016-01-31T23:59:59.999Z. - YEARLY: The start time of each interval is computed as the start of the year in which the first data point occurs, based on a specified time zone (or the default JVM time zone, if no time zone has been specified). The end time of each interval is computed as the end of the year in which the first data point occurs. For instance, if the specified time zone is UTC, and the timestamp of the first data point is 2016-01-05T05:32:00Z, then start of the interval will be computed as 2016-01-01T00:00:00.000Z, while the end of the interval will be computed as 2016-12-31T23:59:59.999Z. This feature also allows for the alignment of intervals that are multiples of one year, one month, one week, or one day. In cases where a given interval is a multiple of more than one time category, the larger time category will be used. For instance, an interval of 24 months will be interpreted as a interval of two years, and will be aligned to the calendar accordingly. As such, if the specified time zone is UTC, and the timestamp of the first data point is 2016-03-05T05:32:00Z, then start of the interval will be computed as 2016-01-01T00:00:00.000Z, while the end of the interval will be computed as 2017-12-31T23:59:59.999Z. This is in keeping with the principle of least astonishment. To specify the time zone for a given HTTP query, include a query string parameter named "tz" with a value equal to a JVM time zone id (e.g. "UTC"). If a time zone is not included in the query string, the default JVM time zone will be used. To specify that a given HTTP query should use the calendar alignment feature for downsampling, include a query string parameter named "use_calendar" with a value of "true". You can stipulate that all HTTP queries should use the calendar alignment feature by including a "tsd.query.downsample.use_calendar" configuration setting within the opentsdb.conf file and by setting its value to "true" (the default value is "false"). This config file setting can be overridden on a per-query basis by including the "use_calendar" parameter in the query string as specified above. (NOTE: Modified by @manolama to fit in with the new 2.3 method params) Signed-off-by: Chris Larsen --- src/core/AggregationIterator.java | 5 +- src/core/Downsampler.java | 135 ++++++- src/core/FillingDownsampler.java | 9 +- src/core/Query.java | 18 + src/core/Span.java | 9 +- src/core/SpanGroup.java | 12 +- src/core/TSQuery.java | 13 + src/core/TsdbQuery.java | 44 +++ src/utils/DateTime.java | 172 ++++++++ test/core/TestDownsampler.java | 546 +++++++++++++++++++++++++- test/core/TestFillingDownsampler.java | 22 +- 11 files changed, 950 insertions(+), 35 deletions(-) diff --git a/src/core/AggregationIterator.java b/src/core/AggregationIterator.java index 03bc93b8cf..64e9179f18 100644 --- a/src/core/AggregationIterator.java +++ b/src/core/AggregationIterator.java @@ -15,6 +15,7 @@ import java.util.Arrays; import java.util.List; import java.util.NoSuchElementException; +import java.util.TimeZone; import com.google.common.annotations.VisibleForTesting; @@ -304,6 +305,8 @@ public static AggregationIterator create(final List spans, final DownsamplingSpecification downsampler, final long query_start, final long query_end, + final TimeZone timezone, + final boolean use_calendar, final boolean rate, final RateOptions rate_options) { final int size = spans.size(); @@ -315,7 +318,7 @@ public static AggregationIterator create(final List spans, it = spans.get(i).spanIterator(); } else { it = spans.get(i).downsampler(start_time, end_time, downsampler, - query_start, query_end); + query_start, query_end, timezone, use_calendar); } if (rate) { it = new RateSpan(it, rate_options); diff --git a/src/core/Downsampler.java b/src/core/Downsampler.java index 06bd421582..8123cb5579 100644 --- a/src/core/Downsampler.java +++ b/src/core/Downsampler.java @@ -13,12 +13,20 @@ package net.opentsdb.core; import java.util.NoSuchElementException; +import java.util.TimeZone; + +import net.opentsdb.utils.DateTime; /** * Iterator that downsamples data points using an {@link Aggregator}. */ public class Downsampler implements SeekableView, DataPoint { + static final long ONE_WEEK_INTERVAL = 604800000L; + static final long ONE_MONTH_INTERVAL = 2592000000L; + static final long ONE_YEAR_INTERVAL = 31536000000L; + static final long ONE_DAY_INTERVAL = 86400000L; + /** The downsampling specification when provided */ protected final DownsamplingSpecification specification; @@ -43,6 +51,9 @@ public class Downsampler implements SeekableView, DataPoint { /** Whether or not to merge all DPs in the source into one vaalue */ protected final boolean run_all; + protected final TimeZone timezone; + protected final boolean use_calendar; + /** * Ctor. * @param source The iterator to access the underlying data. @@ -65,6 +76,8 @@ public class Downsampler implements SeekableView, DataPoint { query_start = 0; query_end = 0; run_all = false; + timezone = TimeZone.getDefault(); + use_calendar = false; } /** @@ -78,13 +91,17 @@ public class Downsampler implements SeekableView, DataPoint { Downsampler(final SeekableView source, final DownsamplingSpecification specification, final long query_start, - final long query_end + final long query_end, + final TimeZone timezone, + final boolean use_calendar ) { this.source = source; this.specification = specification; values_in_interval = new ValuesInInterval(); this.query_start = query_start; this.query_end = query_end; + this.timezone = timezone; + this.use_calendar = use_calendar; final String s = specification.getStringInterval(); if (s != null && s.toLowerCase().contains("all")) { @@ -249,9 +266,13 @@ private void moveToNextValue() { * interval. */ private void resetEndOfInterval() { if (has_next_value_from_source && !run_all) { - // Sets the end of the interval of the timestamp. - timestamp_end_interval = alignTimestamp(next_dp.timestamp()) + - specification.getInterval(); + if (use_calendar && isCalendarInterval()) { + timestamp_end_interval = toEndOfInterval(next_dp.timestamp()); + } else { + // Sets the end of the interval of the timestamp. + timestamp_end_interval = alignTimestamp(next_dp.timestamp()) + + specification.getInterval(); + } } } @@ -269,7 +290,10 @@ void seekInterval(final long timestamp) { // timestamp.. if (run_all) { source.seek(timestamp); - } else { + } else if (use_calendar && isCalendarInterval()) { + source.seek(alignTimestamp(timestamp + toEndOfInterval(timestamp) + - toStartOfInterval(timestamp))); + } else { source.seek(alignTimestamp(timestamp + specification.getInterval() - 1)); } initialized = false; @@ -282,7 +306,9 @@ protected long getIntervalTimestamp() { // provides the correct context for seek. if (run_all) { return timestamp_end_interval; - } else { + } else if (use_calendar && isCalendarInterval()) { + return toStartOfInterval(timestamp_end_interval); + } else { return alignTimestamp(timestamp_end_interval - specification.getInterval()); } @@ -290,9 +316,104 @@ protected long getIntervalTimestamp() { /** Returns timestamp aligned by interval. */ protected long alignTimestamp(final long timestamp) { - return timestamp - (timestamp % specification.getInterval()); + if (use_calendar && isCalendarInterval()) { + return toStartOfInterval(timestamp); + } else { + return timestamp - (timestamp % specification.getInterval()); + } + } + + /** Returns a flag denoting whether the interval can + * be aligned to the calendar */ + private boolean isCalendarInterval () { + if (specification.getInterval() != 0 && + (specification.getInterval() % ONE_YEAR_INTERVAL == 0 || + specification.getInterval() % ONE_MONTH_INTERVAL == 0 || + specification.getInterval() % ONE_WEEK_INTERVAL == 0 || + specification.getInterval() % ONE_DAY_INTERVAL == 0)) { + return true; + } + return false; + } + + /** Returns a timestamp corresponding to the start of the interval + * in which the specified timestamp occurs, aligned to the calendar + * based on the timezone. */ + private long toStartOfInterval(long timestamp) { + if (specification.getInterval() % ONE_YEAR_INTERVAL == 0) { + final long multiplier = specification.getInterval() / ONE_YEAR_INTERVAL; + long result = timestamp; + for (long i = 0; i < multiplier; i++) { + result = DateTime.toStartOfYear(result, timezone) - 1; + } + return result + 1; + } else if (specification.getInterval() % ONE_MONTH_INTERVAL == 0) { + final long multiplier = specification.getInterval() / ONE_MONTH_INTERVAL; + long result = timestamp; + for (long i = 0; i < multiplier; i++) { + result = DateTime.toStartOfMonth(result, timezone) - 1; + } + return result + 1; + } else if (specification.getInterval() % ONE_WEEK_INTERVAL == 0) { + final long multiplier = specification.getInterval() / ONE_WEEK_INTERVAL; + long result = timestamp; + for (long i = 0; i < multiplier; i++) { + result = DateTime.toStartOfWeek(result, timezone) - 1; + } + return result + 1; + } else if (specification.getInterval() % ONE_DAY_INTERVAL == 0) { + final long multiplier = specification.getInterval() / ONE_DAY_INTERVAL; + long result = timestamp; + for (long i = 0; i < multiplier; i++) { + result = DateTime.toStartOfDay(result, timezone) - 1; + } + return result + 1; + } else { + throw new IllegalArgumentException(specification.getInterval() + + " does not correspond to a " + + "an interval that can be aligned to the calendar."); + } } + /** Returns a timestamp corresponding to the end of the interval + * in which the specified timestamp occurs, aligned to the calendar + * based on the timezone. */ + private long toEndOfInterval(long timestamp) { + if (specification.getInterval() % ONE_YEAR_INTERVAL == 0) { + final long multiplier = specification.getInterval() / ONE_YEAR_INTERVAL; + long result = timestamp; + for (long i = 0; i < multiplier; i++) { + result = DateTime.toEndOfYear(result, timezone) + 1; + } + return result - 1; + } else if (specification.getInterval() % ONE_MONTH_INTERVAL == 0) { + final long multiplier = specification.getInterval() / ONE_MONTH_INTERVAL; + long result = timestamp; + for (long i = 0; i < multiplier; i++) { + result = DateTime.toEndOfMonth(result, timezone) + 1; + } + return result - 1; + } else if (specification.getInterval() % ONE_WEEK_INTERVAL == 0) { + final long multiplier = specification.getInterval() / ONE_WEEK_INTERVAL; + long result = timestamp; + for (long i = 0; i < multiplier; i++) { + result = DateTime.toEndOfWeek(result, timezone) + 1; + } + return result - 1; + } else if (specification.getInterval() % ONE_DAY_INTERVAL == 0) { + final long multiplier = specification.getInterval() / ONE_DAY_INTERVAL; + long result = timestamp; + for (long i = 0; i < multiplier; i++) { + result = DateTime.toEndOfDay(result, timezone) + 1; + } + return result - 1; + } else { + throw new IllegalArgumentException(specification.getInterval() + + " does not correspond to a " + + "an interval that can be aligned to the calendar."); + } + } + // ---------------------- // // Doubles interface // // ---------------------- // diff --git a/src/core/FillingDownsampler.java b/src/core/FillingDownsampler.java index bcba99f61a..2de42bb734 100644 --- a/src/core/FillingDownsampler.java +++ b/src/core/FillingDownsampler.java @@ -13,6 +13,7 @@ package net.opentsdb.core; import java.util.NoSuchElementException; +import java.util.TimeZone; /** * A specialized downsampler that returns special values, based on the fill @@ -44,7 +45,7 @@ public class FillingDownsampler extends Downsampler { final Aggregator downsampler, final FillPolicy fill_policy) { this(source, start_time, end_time, new DownsamplingSpecification(interval_ms, downsampler, fill_policy) - , 0, 0); + , 0, 0, TimeZone.getDefault(), false); } /** @@ -60,9 +61,11 @@ public class FillingDownsampler extends Downsampler { */ FillingDownsampler(final SeekableView source, final long start_time, final long end_time, final DownsamplingSpecification specification, - final long query_start, final long end_start) { + final long query_start, final long end_start, + final TimeZone timezone, + final boolean use_calendar) { // Lean on the superclass implementation. - super(source, specification, query_start, end_start); + super(source, specification, query_start, end_start, timezone, use_calendar); // Ensure we aren't given a bogus fill policy. if (FillPolicy.NONE == specification.getFillPolicy()) { diff --git a/src/core/Query.java b/src/core/Query.java index 34553c591e..2d1db08f83 100644 --- a/src/core/Query.java +++ b/src/core/Query.java @@ -67,6 +67,24 @@ public interface Query { */ long getEndTime(); + /** + * Sets the timezone to use for aligning intervals based on the calendar. + * @param timezone the timezone to use + */ + void setTimezone(String timezone); + + /** @return the timezone to use for aligning intervals based on the calendar. */ + String getTimezone(); + + /** + * Sets a flag denoting whether or not to align intervals based on the calendar. + * @param use_calendar true, if the intervals should be aligned based on the calendar; false, otherwise + */ + void setUseCalendar(boolean use_calendar); + + /** @return A flag denoting whether or not to align intervals based on the calendar. */ + boolean getUseCalendar(); + /** * Sets whether or not the data queried will be deleted. * @param delete True if data should be deleted, false otherwise. diff --git a/src/core/Span.java b/src/core/Span.java index 15338956ae..a3d9d4894e 100644 --- a/src/core/Span.java +++ b/src/core/Span.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Map; import java.util.NoSuchElementException; +import java.util.TimeZone; import net.opentsdb.meta.Annotation; import net.opentsdb.uid.UniqueId; @@ -499,16 +500,18 @@ Downsampler downsampler(final long start_time, final long end_time, final DownsamplingSpecification downsampler, final long query_start, - final long query_end) { + final long query_end, + final TimeZone timezone, + final boolean use_calendar) { if (downsampler == null) { return null; } if (FillPolicy.NONE == downsampler.getFillPolicy()) { return new Downsampler(spanIterator(), downsampler, - query_start, query_end); + query_start, query_end, timezone, use_calendar); } return new FillingDownsampler(spanIterator(), start_time, end_time, - downsampler, query_start, query_end); + downsampler, query_start, query_end, timezone, use_calendar); } public int getQueryIndex() { diff --git a/src/core/SpanGroup.java b/src/core/SpanGroup.java index 90133dcf51..a24eb1e369 100644 --- a/src/core/SpanGroup.java +++ b/src/core/SpanGroup.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TimeZone; import org.hbase.async.Bytes; import org.hbase.async.Bytes.ByteMap; @@ -106,6 +107,9 @@ final class SpanGroup implements DataPoints { /** The TSDB to which we belong, used for resolution */ private final TSDB tsdb; + private final TimeZone timezone; + private final boolean use_calendar; + /** * Ctor. * @param tsdb The TSDB we belong to. @@ -193,7 +197,7 @@ final class SpanGroup implements DataPoints { downsampler != null ? new DownsamplingSpecification(interval, downsampler, fill_policy) : null, - query_index, 0, 0); + 0, 0, TimeZone.getDefault(), false, query_index); } /** @@ -225,6 +229,8 @@ final class SpanGroup implements DataPoints { final DownsamplingSpecification downsampler, final long query_start, final long query_end, + final TimeZone timezone, + final boolean use_calendar, final int query_index) { annotations = new ArrayList(); this.start_time = (start_time & Const.SECOND_MASK) == 0 ? @@ -244,6 +250,8 @@ final class SpanGroup implements DataPoints { this.query_end = query_end; this.query_index = query_index; this.tsdb = tsdb; + this.timezone = timezone; + this.use_calendar = use_calendar; } /** @@ -487,7 +495,7 @@ public SeekableView iterator() { return AggregationIterator.create(spans, start_time, end_time, aggregator, aggregator.interpolationMethod(), downsampler, query_start, query_end, - rate, rate_options); + timezone, use_calendar, rate, rate_options); } /** diff --git a/src/core/TSQuery.java b/src/core/TSQuery.java index 6ae15fc789..80a4a11fc0 100644 --- a/src/core/TSQuery.java +++ b/src/core/TSQuery.java @@ -95,6 +95,9 @@ public final class TSQuery { /** Whether or not to delete the queried data */ private boolean delete = false; + /** A flag denoting whether or not to align intervals based on the calendar */ + private boolean use_calendar; + /** The query status for tracking over all performance of this query */ private QueryStats query_stats; @@ -362,6 +365,11 @@ public boolean getDelete() { return this.delete; } + /** @return the flag denoting whether intervals should be aligned based on the calendar */ + public boolean getUseCalendar() { + return use_calendar; + } + /** @return the query stats object. Ignored during JSON serialization */ @JsonIgnore public QueryStats getQueryStats() { @@ -447,6 +455,11 @@ public void setDelete(boolean delete) { this.delete = delete; } + /** @param use_calendar a flag denoting whether or not to align intervals based on the calendar */ + public void setUseCalendar(boolean use_calendar) { + this.use_calendar = use_calendar; + } + /** @param query_stats the query stats object to associate with this query */ public void setQueryStats(final QueryStats query_stats) { this.query_stats = query_stats; diff --git a/src/core/TsdbQuery.java b/src/core/TsdbQuery.java index 105cecf8c2..5282dd4f8c 100644 --- a/src/core/TsdbQuery.java +++ b/src/core/TsdbQuery.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TimeZone; import java.util.TreeMap; import org.slf4j.Logger; @@ -131,6 +132,12 @@ final class TsdbQuery implements Query { /** Tag value filters to apply post scan */ private List filters; + /** The timezone to use for aligning intervals based on the calendar */ + private String timezone; + + /** A flag denoting whether or not to align intervals based on the calendar */ + private boolean use_calendar; + /** An object for storing stats in regarding the query. May be null */ private QueryStats query_stats; @@ -303,6 +310,35 @@ public void setTimeSeries(final List tsuids, this.rate_options = rate_options; } + /** + * Sets the timezone to use for aligning intervals based on the calendar. + * @param timezone the timezone to use + */ + public void setTimezone(String timezone) { + this.timezone = timezone; + } + + /** @return the timezone to use for aligning intervals based on the calendar. */ + @Override + public String getTimezone() { + return this.timezone; + } + + /** + * Sets a flag denoting whether or not to align intervals based on the calendar. + * @param use_calendar true, if the intervals should be aligned based on the calendar; false, otherwise + */ + @Override + public void setUseCalendar(boolean use_calendar) { + this.use_calendar = use_calendar; + } + + /** @return A flag denoting whether or not to align intervals based on the calendar. */ + @Override + public boolean getUseCalendar() { + return this.use_calendar; + } + /** * @param explicit_tags Whether or not to match only on the given tags * @since 2.3 @@ -325,6 +361,8 @@ public Deferred configureFromQuery(final TSQuery query, setStartTime(query.startTime()); setEndTime(query.endTime()); setDelete(query.getDelete()); + setTimezone(query.getTimezone()); + setUseCalendar(query.getUseCalendar()); query_index = index; query_stats = query.getQueryStats(); @@ -853,6 +891,8 @@ public DataPoints[] call(final TreeMap spans) throws Exception { downsampler, getStartTime(), getEndTime(), + timezone != null ? DateTime.timezones.get(timezone) : TimeZone.getDefault(), + use_calendar, query_index); group.add(span); groups[i++] = group; @@ -872,6 +912,8 @@ public DataPoints[] call(final TreeMap spans) throws Exception { downsampler, getStartTime(), getEndTime(), + timezone != null ? DateTime.timezones.get(timezone) : TimeZone.getDefault(), + use_calendar, query_index); if (query_stats != null) { query_stats.addStat(query_index, QueryStat.GROUP_BY_TIME, 0); @@ -922,6 +964,8 @@ public DataPoints[] call(final TreeMap spans) throws Exception { downsampler, getStartTime(), getEndTime(), + timezone != null ? DateTime.timezones.get(timezone) : TimeZone.getDefault(), + use_calendar, query_index); // Copy the array because we're going to keep `group' and overwrite // its contents. So we want the collection to have an immutable copy. diff --git a/src/utils/DateTime.java b/src/utils/DateTime.java index d3d9a08c44..d1386a45fa 100644 --- a/src/utils/DateTime.java +++ b/src/utils/DateTime.java @@ -14,6 +14,7 @@ import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.Calendar; import java.util.HashMap; import java.util.TimeZone; @@ -305,4 +306,175 @@ public static double msFromNanoDiff(final long end, final long start) { } return ((double) end - (double) start) / 1000000; } + + /** + * Returns a timestamp corresponding the beginning of the year in which the specified + * timestamp occurs. This operation is performed based on the specified time zone. + * @param timestamp the epoch time + * @param time_zone the time zone used to determine the beginning of the year + * @return the epoch time corresponding to the beginning of the year + */ + public static long toStartOfYear(final long timestamp, final TimeZone time_zone) { + final Calendar c = getStartOfYear(timestamp, time_zone); + return c.getTimeInMillis(); + } + + /** + * Returns a timestamp corresponding the end of the year in which the specified + * timestamp occurs. This operation is performed based on the specified time zone. + * @param timestamp the epoch time + * @param time_zone the time zone used to determine the end of the year + * @return the epoch time corresponding to the end of the year + */ + public static long toEndOfYear(final long timestamp, final TimeZone time_zone) { + final Calendar c = getStartOfYear(timestamp, time_zone); + c.add(Calendar.YEAR, 1); + return c.getTimeInMillis() - 1; + } + + /** + * Returns a timestamp corresponding the beginning of the month in which the specified + * timestamp occurs. This operation is performed based on the specified time zone. + * @param timestamp the epoch time + * @param time_zone the time zone used to determine the beginning of the month + * @return the epoch time corresponding to the beginning of the month + */ + public static long toStartOfMonth(final long timestamp, final TimeZone time_zone) { + final Calendar c = getStartOfMonth(timestamp, time_zone); + return c.getTimeInMillis(); + } + + /** + * Returns a timestamp corresponding the end of the month in which the specified + * timestamp occurs. This operation is performed based on the specified time zone. + * @param timestamp the epoch time + * @param time_zone the time zone used to determine the end of the month + * @return the epoch time corresponding to the end of the month + */ + public static long toEndOfMonth(final long timestamp, final TimeZone time_zone) { + final Calendar c = getStartOfMonth(timestamp, time_zone); + c.add(Calendar.MONTH, 1); + return c.getTimeInMillis() - 1; + } + + /** + * Returns a timestamp corresponding the beginning of the week in which the specified + * timestamp occurs. This operation is performed based on the specified time zone. + * @param timestamp the epoch time + * @param time_zone the time zone used to determine the beginning of the week + * @return the epoch time corresponding to the beginning of the week + */ + public static long toStartOfWeek(final long timestamp, final TimeZone time_zone) { + final Calendar c = getStartOfWeek(timestamp, time_zone); + return c.getTimeInMillis(); + } + + /** + * Returns a timestamp corresponding the end of the week in which the specified + * timestamp occurs. This operation is performed based on the specified time zone. + * @param timestamp the epoch time + * @param time_zone the time zone used to determine the end of the week + * @return the epoch time corresponding to the end of the week + */ + public static long toEndOfWeek(final long timestamp, final TimeZone time_zone) { + final Calendar c = getStartOfWeek(timestamp, time_zone); + c.add(Calendar.DATE, 7); + return c.getTimeInMillis() - 1; + } + + /** + * Returns a timestamp corresponding the beginning of the day in which the specified + * timestamp occurs. This operation is performed based on the specified time zone. + * @param timestamp the epoch time + * @param time_zone the time zone used to determine the beginning of the day + * @return the epoch time corresponding to the beginning of the day + */ + public static long toStartOfDay(final long timestamp, final TimeZone time_zone) { + final Calendar c = getStartOfDay(timestamp, time_zone); + return c.getTimeInMillis(); + } + + /** + * Returns a timestamp corresponding the end of the day in which the specified + * timestamp occurs. This operation is performed based on the specified time zone. + * @param timestamp the epoch time + * @param time_zone the time zone used to determine the end of the day + * @return the epoch time corresponding to the end of the day + */ + public static long toEndOfDay(final long timestamp, final TimeZone time_zone) { + final Calendar c = getStartOfDay(timestamp, time_zone); + c.add(Calendar.DATE, 1); + return c.getTimeInMillis() - 1; + } + + /** + * Returns a Calendar object corresponding the beginning of the year in which the specified + * timestamp occurs. This operation is performed based on the specified time zone. + * @param timestamp the epoch time + * @param time_zone the time zone used to determine the beginning of the year + * @return a Calendar object corresponding to the beginning of the year + */ + private static Calendar getStartOfYear(final long timestamp, final TimeZone time_zone) { + final Calendar c = Calendar.getInstance(time_zone); + c.setTimeInMillis(timestamp); + c.set(Calendar.DAY_OF_YEAR, 1); + c.set(Calendar.HOUR_OF_DAY, 0); + c.set(Calendar.MINUTE, 0); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + return c; + } + + /** + * Returns a Calendar object corresponding the beginning of the month in which the specified + * timestamp occurs. This operation is performed based on the specified time zone. + * @param timestamp the epoch time + * @param time_zone the time zone used to determine the beginning of the month + * @return a Calendar object corresponding to the beginning of the month + */ + private static Calendar getStartOfMonth(final long timestamp, final TimeZone time_zone) { + final Calendar c = Calendar.getInstance(time_zone); + c.setTimeInMillis(timestamp); + c.set(Calendar.DAY_OF_MONTH, 1); + c.set(Calendar.HOUR_OF_DAY, 0); + c.set(Calendar.MINUTE, 0); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + return c; + } + + /** + * Returns a Calendar object corresponding the beginning of the week in which the specified + * timestamp occurs. This operation is performed based on the specified time zone. + * @param timestamp the epoch time + * @param time_zone the time zone used to determine the beginning of the week + * @return a Calendar object corresponding to the beginning of the week + */ + private static Calendar getStartOfWeek(final long timestamp, final TimeZone time_zone) { + final Calendar c = Calendar.getInstance(time_zone); + c.setTimeInMillis(timestamp); + c.set(Calendar.DAY_OF_WEEK, 1); // 1-sun, 2-mon. + c.set(Calendar.HOUR_OF_DAY, 0); + c.set(Calendar.MINUTE, 0); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + return c; + } + + /** + * Returns a Calendar object corresponding the beginning of the day in which the specified + * timestamp occurs. This operation is performed based on the specified time zone. + * @param timestamp the epoch time + * @param time_zone the time zone used to determine the beginning of the day + * @return a Calendar object corresponding to the beginning of the day + */ + private static Calendar getStartOfDay(final long timestamp, final TimeZone time_zone) { + final Calendar c = Calendar.getInstance(time_zone); + c.setTimeInMillis(timestamp); + c.set(Calendar.HOUR_OF_DAY, 0); + c.set(Calendar.MINUTE, 0); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + return c; + } } diff --git a/test/core/TestDownsampler.java b/test/core/TestDownsampler.java index 97b54b02de..ccaa1eba5a 100644 --- a/test/core/TestDownsampler.java +++ b/test/core/TestDownsampler.java @@ -19,7 +19,10 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import java.util.Arrays; +import java.util.Calendar; import java.util.List; +import java.util.TimeZone; import com.google.common.collect.Lists; @@ -52,7 +55,9 @@ public class TestDownsampler { (int)DateTime.parseDuration("10s"); private static final Aggregator AVG = Aggregators.get("avg"); private static final Aggregator SUM = Aggregators.get("sum"); - + private static final TimeZone UTC_TIME_ZONE = DateTime.timezones.get("UTC"); + private static final TimeZone EST_TIME_ZONE = DateTime.timezones.get("EST"); + private SeekableView source; private Downsampler downsampler; private DownsamplingSpecification specification; @@ -65,7 +70,7 @@ public void before() { @Test public void testDownsampler() { specification = new DownsamplingSpecification("1000s-avg"); - downsampler = new Downsampler(source, specification, 0, 0); + downsampler = new Downsampler(source, specification, 0, 0, TimeZone.getDefault(), false); verify(source, never()).next(); List values = Lists.newArrayList(); List timestamps_in_millis = Lists.newArrayList(); @@ -146,7 +151,7 @@ public void testDownsampler_10seconds() { MutableDataPoint.ofDoubleValue(BASE_TIME + 5000L * 10, 1024) })); specification = new DownsamplingSpecification("10s-sum"); - downsampler = new Downsampler(source, specification, 0, 0); + downsampler = new Downsampler(source, specification, 0, 0, TimeZone.getDefault(), false); verify(source, never()).next(); List values = Lists.newArrayList(); List timestamps_in_millis = Lists.newArrayList(); @@ -215,7 +220,7 @@ public void testDownsampler_15seconds() { MutableDataPoint.ofLongValue(BASE_TIME + 55000L, 32) })); specification = new DownsamplingSpecification("15s-sum"); - downsampler = new Downsampler(source, specification, 0, 0); + downsampler = new Downsampler(source, specification, 0, 0, TimeZone.getDefault(), false); verify(source, never()).next(); List values = Lists.newArrayList(); List timestamps_in_millis = Lists.newArrayList(); @@ -248,8 +253,8 @@ public void testDownsampler_allFullRange() { MutableDataPoint.ofLongValue(BASE_TIME + 55000L, 32) })); specification = new DownsamplingSpecification("0all-sum"); - downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); - System.out.println(downsampler); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE, TimeZone.getDefault(), false); + verify(source, never()).next(); List values = Lists.newArrayList(); List timestamps_in_millis = Lists.newArrayList(); @@ -277,7 +282,7 @@ public void testDownsampler_allFilterOnQuery() { })); specification = new DownsamplingSpecification("0all-sum"); downsampler = new Downsampler(source, specification, - BASE_TIME + 15000L, BASE_TIME + 45000L); + BASE_TIME + 15000L, BASE_TIME + 45000L, TimeZone.getDefault(), false); verify(source, never()).next(); List values = Lists.newArrayList(); List timestamps_in_millis = Lists.newArrayList(); @@ -305,7 +310,7 @@ public void testDownsampler_allFilterOnQueryOutOfRangeEarly() { })); specification = new DownsamplingSpecification("0all-sum"); downsampler = new Downsampler(source, specification, - BASE_TIME + 65000L, BASE_TIME + 75000L); + BASE_TIME + 65000L, BASE_TIME + 75000L, TimeZone.getDefault(), false); verify(source, never()).next(); List values = Lists.newArrayList(); List timestamps_in_millis = Lists.newArrayList(); @@ -331,7 +336,7 @@ public void testDownsampler_allFilterOnQueryOutOfRangeLate() { })); specification = new DownsamplingSpecification("0all-sum"); downsampler = new Downsampler(source, specification, - BASE_TIME - 15000L, BASE_TIME - 5000L); + BASE_TIME - 15000L, BASE_TIME - 5000L, TimeZone.getDefault(), false); verify(source, never()).next(); List values = Lists.newArrayList(); List timestamps_in_millis = Lists.newArrayList(); @@ -345,6 +350,465 @@ public void testDownsampler_allFilterOnQueryOutOfRangeLate() { assertEquals(0, values.size()); } + @Test + public void testDownsampler_calendar() { + source = spy(SeekableViewsForTest.fromArray(new DataPoint[] { + MutableDataPoint.ofLongValue(BASE_TIME + 5000L, 1), + MutableDataPoint.ofLongValue(BASE_TIME + 15000L, 2), + MutableDataPoint.ofLongValue(BASE_TIME + 25000L, 4), + MutableDataPoint.ofLongValue(BASE_TIME + 35000L, 8), + MutableDataPoint.ofLongValue(BASE_TIME + 45000L, 16), + MutableDataPoint.ofLongValue(BASE_TIME + 55000L, 32) + })); + specification = new DownsamplingSpecification("1d-sum"); + //specification.setTimezone(DateTime.timezones.get("America/Denver")); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE, DateTime.timezones.get("America/Denver"), true); + verify(source, never()).next(); + List values = Lists.newArrayList(); + List timestamps_in_millis = Lists.newArrayList(); + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + values.add(dp.doubleValue()); + timestamps_in_millis.add(dp.timestamp()); + } + + assertEquals(1, values.size()); + assertEquals(63, values.get(0), 0.0000001); + assertEquals(1356937200000L, timestamps_in_millis.get(0).longValue()); + } + + @Test + public void testDownsampler_noData() { + source = spy(SeekableViewsForTest.fromArray(new DataPoint[] { })); + specification = new DownsamplingSpecification("1d-sum"); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE, TimeZone.getDefault(), false); + verify(source, never()).next(); + assertFalse(downsampler.hasNext()); + } + + @Test + public void testDownsampler_noDataCalendar() { + source = spy(SeekableViewsForTest.fromArray(new DataPoint[] { })); + specification = new DownsamplingSpecification("1m-sum"); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE, + UTC_TIME_ZONE, true); + verify(source, never()).next(); + assertFalse(downsampler.hasNext()); + } + + @Test + public void testDownsampler_1day() { + final DataPoint [] data_points = new DataPoint[4]; + long timestamp = DateTime.toStartOfDay(BASE_TIME, UTC_TIME_ZONE); + for (int i = 0; i < data_points.length; i++) { + long value = 1 << i; + data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); + + i += 1; + long startOfNextInterval = DateTime.toEndOfDay(timestamp, UTC_TIME_ZONE) + 1; + timestamp = timestamp + (startOfNextInterval - timestamp) / 2; + value = 1 << i; + data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); + timestamp = startOfNextInterval; + } + + System.out.println(Arrays.toString(data_points)); + + source = spy(SeekableViewsForTest.fromArray(data_points)); + + downsampler = new Downsampler(source, Downsampler.ONE_DAY_INTERVAL, SUM); + verify(source, never()).next(); + List values = Lists.newArrayList(); + List timestamps_in_millis = Lists.newArrayList(); + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + values.add(dp.doubleValue()); + timestamps_in_millis.add(dp.timestamp()); + } + + assertEquals(2, values.size()); + timestamp = DateTime.toStartOfDay(BASE_TIME, UTC_TIME_ZONE); + for (int i = 0, j = 0; i < values.size(); i++) { + assertEquals((1 << j++) + (1 << j++), values.get(i), 0.0000001); + assertEquals(timestamp, timestamps_in_millis.get(i).longValue()); + timestamp = DateTime.toEndOfDay(timestamp, UTC_TIME_ZONE) + 1; + } + } + + @Test + public void testDownsampler_1day_timezone() { + final DataPoint [] data_points = new DataPoint[4]; + long timestamp = DateTime.toStartOfDay(BASE_TIME, UTC_TIME_ZONE) - EST_TIME_ZONE.getOffset(BASE_TIME); + for (int i = 0; i < data_points.length; i++) { + long value = 1 << i; + data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); + + i += 1; + long startOfNextInterval = DateTime.toEndOfDay(timestamp, EST_TIME_ZONE) + 1; + timestamp = timestamp + (startOfNextInterval - timestamp) / 2; + value = 1 << i; + data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); + timestamp = startOfNextInterval; + } + + source = spy(SeekableViewsForTest.fromArray(data_points)); + specification = new DownsamplingSpecification("1d-sum"); + downsampler = new Downsampler(source, specification, 0, 0, EST_TIME_ZONE, true); + verify(source, never()).next(); + List values = Lists.newArrayList(); + List timestamps_in_millis = Lists.newArrayList(); + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + values.add(dp.doubleValue()); + timestamps_in_millis.add(dp.timestamp()); + } + + assertEquals(2, values.size()); + timestamp = DateTime.toStartOfDay(BASE_TIME, UTC_TIME_ZONE) - EST_TIME_ZONE.getOffset(BASE_TIME); + for (int i = 0, j = 0; i < values.size(); i++) { + assertEquals((1 << j++) + (1 << j++), values.get(i), 0.0000001); + assertEquals(timestamp, timestamps_in_millis.get(i).longValue()); + timestamp = DateTime.toEndOfDay(timestamp, EST_TIME_ZONE) + 1; + } + } + + @Test + public void testDownsampler_1week() { + final DataPoint [] data_points = new DataPoint[4]; + long timestamp = DateTime.toStartOfWeek(BASE_TIME, UTC_TIME_ZONE); + for (int i = 0; i < data_points.length; i++) { + long value = 1 << i; + data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); + + i += 1; + long startOfNextInterval = DateTime.toEndOfWeek(timestamp, UTC_TIME_ZONE) + 1; + timestamp = timestamp + (startOfNextInterval - timestamp) / 2; + value = 1 << i; + data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); + timestamp = startOfNextInterval; + } + + source = spy(SeekableViewsForTest.fromArray(data_points)); + + specification = new DownsamplingSpecification("1w-sum"); + downsampler = new Downsampler(source, specification, 0, 0, UTC_TIME_ZONE, true); + verify(source, never()).next(); + List values = Lists.newArrayList(); + List timestamps_in_millis = Lists.newArrayList(); + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + values.add(dp.doubleValue()); + timestamps_in_millis.add(dp.timestamp()); + } + + assertEquals(2, values.size()); + timestamp = DateTime.toStartOfWeek(BASE_TIME, UTC_TIME_ZONE); + for (int i = 0, j = 0; i < values.size(); i++) { + assertEquals((1 << j++) + (1 << j++), values.get(i), 0.0000001); + assertEquals(timestamp, timestamps_in_millis.get(i).longValue()); + timestamp = DateTime.toEndOfWeek(timestamp, UTC_TIME_ZONE) + 1; + } + } + + @Test + public void testDownsampler_1week_timezone() { + final DataPoint [] data_points = new DataPoint[4]; + long timestamp = DateTime.toStartOfWeek(BASE_TIME, UTC_TIME_ZONE) - EST_TIME_ZONE.getOffset(BASE_TIME); + for (int i = 0; i < data_points.length; i++) { + long value = 1 << i; + data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); + + i += 1; + long startOfNextInterval = DateTime.toEndOfWeek(timestamp, EST_TIME_ZONE) + 1; + timestamp = timestamp + (startOfNextInterval - timestamp) / 2; + value = 1 << i; + data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); + timestamp = startOfNextInterval; + } + + source = spy(SeekableViewsForTest.fromArray(data_points)); + + specification = new DownsamplingSpecification("1w-sum"); + downsampler = new Downsampler(source, specification, 0, 0, EST_TIME_ZONE, true); + verify(source, never()).next(); + List values = Lists.newArrayList(); + List timestamps_in_millis = Lists.newArrayList(); + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + values.add(dp.doubleValue()); + timestamps_in_millis.add(dp.timestamp()); + } + + assertEquals(2, values.size()); + timestamp = DateTime.toStartOfWeek(BASE_TIME, UTC_TIME_ZONE) - EST_TIME_ZONE.getOffset(BASE_TIME); + for (int i = 0, j = 0; i < values.size(); i++) { + assertEquals((1 << j++) + (1 << j++), values.get(i), 0.0000001); + assertEquals(timestamp, timestamps_in_millis.get(i).longValue()); + timestamp = DateTime.toEndOfWeek(timestamp, EST_TIME_ZONE) + 1; + } + } + + @Test + public void testDownsampler_1month() { + final DataPoint [] data_points = new DataPoint[24]; + long timestamp = DateTime.toStartOfMonth(BASE_TIME, UTC_TIME_ZONE); + for (int i = 0; i < data_points.length; i++) { + long value = 1 << i; + data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); + + i += 1; + long startOfNextInterval = DateTime.toEndOfMonth(timestamp, UTC_TIME_ZONE) + 1; + timestamp = timestamp + (startOfNextInterval - timestamp) / 2; + value = 1 << i; + data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); + timestamp = startOfNextInterval; + } + + source = spy(SeekableViewsForTest.fromArray(data_points)); + + specification = new DownsamplingSpecification("1n-sum"); + downsampler = new Downsampler(source, specification, 0, 0, UTC_TIME_ZONE, true); + verify(source, never()).next(); + List values = Lists.newArrayList(); + List timestamps_in_millis = Lists.newArrayList(); + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + values.add(dp.doubleValue()); + timestamps_in_millis.add(dp.timestamp()); + } + + assertEquals(12, values.size()); + timestamp = DateTime.toStartOfMonth(BASE_TIME, UTC_TIME_ZONE); + for (int i = 0, j = 0; i < values.size(); i++) { + assertEquals((1 << j++) + (1 << j++), values.get(i), 0.0000001); + assertEquals(timestamp, timestamps_in_millis.get(i).longValue()); + timestamp = DateTime.toEndOfMonth(timestamp, UTC_TIME_ZONE) + 1; + } + } + + @Test + public void testDownsampler_1month_alt() { + /* + 1380600000 -> 2013-10-01T04:00:00Z + 1383278400 -> 2013-11-01T04:00:00Z + 1385874000 -> 2013-12-01T05:00:00Z + 1388552400 -> 2014-01-01T05:00:00Z + 1391230800 -> 2014-02-01T05:00:00Z + 1393650000 -> 2014-03-01T05:00:00Z + 1396324800 -> 2014-04-01T04:00:00Z + 1398916800 -> 2014-05-01T04:00:00Z + 1401595200 -> 2014-06-01T04:00:00Z + 1404187200 -> 2014-07-01T04:00:00Z + 1406865600 -> 2014-08-01T04:00:00Z + 1409544000 -> 2014-09-01T04:00:00Z + */ + + int value = 1; + final DataPoint [] data_points = new DataPoint[] { + MutableDataPoint.ofLongValue(1380600000000L, value), + MutableDataPoint.ofLongValue(1383278400000L, value), + MutableDataPoint.ofLongValue(1385874000000L, value), + MutableDataPoint.ofLongValue(1388552400000L, value), + MutableDataPoint.ofLongValue(1391230800000L, value), + MutableDataPoint.ofLongValue(1393650000000L, value), + MutableDataPoint.ofLongValue(1396324800000L, value), + MutableDataPoint.ofLongValue(1398916800000L, value), + MutableDataPoint.ofLongValue(1401595200000L, value), + MutableDataPoint.ofLongValue(1404187200000L, value), + MutableDataPoint.ofLongValue(1406865600000L, value), + MutableDataPoint.ofLongValue(1409544000000L, value), + }; + + source = spy(SeekableViewsForTest.fromArray(data_points)); + + specification = new DownsamplingSpecification("1d-sum"); + downsampler = new Downsampler(source, specification, 0, 0, UTC_TIME_ZONE, true); + verify(source, never()).next(); + List values = Lists.newArrayList(); + List timestamps_in_millis = Lists.newArrayList(); + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + values.add(dp.doubleValue()); + timestamps_in_millis.add(dp.timestamp()); + } + + assertEquals(12, values.size()); + long timestamp = DateTime.toStartOfMonth(data_points[0].timestamp(), UTC_TIME_ZONE); + for (int i = 0; i < values.size(); i++) { + assertEquals(1, values.get(i), 0.0000001); + assertEquals(timestamp, timestamps_in_millis.get(i).longValue()); + timestamp = DateTime.toEndOfMonth(timestamp, UTC_TIME_ZONE) + 1; + } + } + + @Test + public void testDownsampler_2months() { + final DataPoint [] data_points = new DataPoint[24]; + long timestamp = DateTime.toStartOfMonth(BASE_TIME, UTC_TIME_ZONE); + for (int i = 0; i < data_points.length; i++) { + long value = 1 << i; + data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); + + i += 1; + long startOfNextInterval = DateTime.toEndOfMonth(timestamp, UTC_TIME_ZONE) + 1; + timestamp = timestamp + (startOfNextInterval - timestamp) / 2; + value = 1 << i; + data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); + timestamp = startOfNextInterval; + } + + source = spy(SeekableViewsForTest.fromArray(data_points)); + + specification = new DownsamplingSpecification("2n-sum"); + downsampler = new Downsampler(source, specification, 0, 0, UTC_TIME_ZONE, true); + verify(source, never()).next(); + List values = Lists.newArrayList(); + List timestamps_in_millis = Lists.newArrayList(); + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + values.add(dp.doubleValue()); + timestamps_in_millis.add(dp.timestamp()); + } + + assertEquals(6, values.size()); + timestamp = DateTime.toStartOfMonth(BASE_TIME, UTC_TIME_ZONE); + for (int i = 0, j = 0; i < values.size(); i++) { + long value = 0; + for (int k = 0; k < 4; k++) { + value += (1 << j++); + } + assertEquals(value, values.get(i), 0.0000001); + assertEquals(timestamp, timestamps_in_millis.get(i).longValue()); + timestamp = DateTime.toEndOfMonth(timestamp, UTC_TIME_ZONE) + 1; + timestamp = DateTime.toEndOfMonth(timestamp, UTC_TIME_ZONE) + 1; + } + } + + @Test + public void testDownsampler_1month_timezone() { + final DataPoint [] data_points = new DataPoint[24]; + long timestamp = DateTime.toStartOfMonth(BASE_TIME, UTC_TIME_ZONE) - EST_TIME_ZONE.getOffset(BASE_TIME); + for (int i = 0; i < data_points.length; i++) { + long value = 1 << i; + data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); + + i += 1; + long startOfNextInterval = DateTime.toEndOfMonth(timestamp, EST_TIME_ZONE) + 1; + timestamp = timestamp + (startOfNextInterval - timestamp) / 2; + value = 1 << i; + data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); + timestamp = startOfNextInterval; + } + + source = spy(SeekableViewsForTest.fromArray(data_points)); + + specification = new DownsamplingSpecification("1n-sum"); + downsampler = new Downsampler(source, specification, 0, 0, EST_TIME_ZONE, true); + verify(source, never()).next(); + List values = Lists.newArrayList(); + List timestamps_in_millis = Lists.newArrayList(); + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + values.add(dp.doubleValue()); + timestamps_in_millis.add(dp.timestamp()); + } + + assertEquals(12, values.size()); + timestamp = DateTime.toStartOfMonth(BASE_TIME, UTC_TIME_ZONE) - EST_TIME_ZONE.getOffset(BASE_TIME); + for (int i = 0, j = 0; i < values.size(); i++) { + assertEquals((1 << j++) + (1 << j++), values.get(i), 0.0000001); + assertEquals(timestamp, timestamps_in_millis.get(i).longValue()); + timestamp = DateTime.toEndOfMonth(timestamp, EST_TIME_ZONE) + 1; + } + } + + @Test + public void testDownsampler_1year() { + final DataPoint [] data_points = new DataPoint[4]; + long timestamp = DateTime.toStartOfYear(BASE_TIME, UTC_TIME_ZONE); + for (int i = 0; i < data_points.length; i++) { + long value = 1 << i; + data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); + + i += 1; + long startOfNextInterval = DateTime.toEndOfYear(timestamp, UTC_TIME_ZONE) + 1; + timestamp = timestamp + (startOfNextInterval - timestamp) / 2; + value = 1 << i; + data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); + timestamp = startOfNextInterval; + } + + source = spy(SeekableViewsForTest.fromArray(data_points)); + + specification = new DownsamplingSpecification("1y-sum"); + downsampler = new Downsampler(source, specification, 0, 0, UTC_TIME_ZONE, true); + verify(source, never()).next(); + List values = Lists.newArrayList(); + List timestamps_in_millis = Lists.newArrayList(); + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + values.add(dp.doubleValue()); + timestamps_in_millis.add(dp.timestamp()); + } + + assertEquals(2, values.size()); + timestamp = DateTime.toStartOfYear(BASE_TIME, UTC_TIME_ZONE); + for (int i = 0, j = 0; i < values.size(); i++) { + assertEquals((1 << j++) + (1 << j++), values.get(i), 0.0000001); + assertEquals(timestamp, timestamps_in_millis.get(i).longValue()); + timestamp = DateTime.toEndOfYear(timestamp, UTC_TIME_ZONE) + 1; + } + } + + @Test + public void testDownsampler_1year_timezone() { + final DataPoint [] data_points = new DataPoint[4]; + long timestamp = DateTime.toStartOfYear(BASE_TIME, UTC_TIME_ZONE) - EST_TIME_ZONE.getOffset(BASE_TIME); + for (int i = 0; i < data_points.length; i++) { + long value = 1 << i; + data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); + + i += 1; + long startOfNextInterval = DateTime.toEndOfYear(timestamp, EST_TIME_ZONE) + 1; + timestamp = timestamp + (startOfNextInterval - timestamp) / 2; + value = 1 << i; + data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); + timestamp = startOfNextInterval; + } + + source = spy(SeekableViewsForTest.fromArray(data_points)); + + specification = new DownsamplingSpecification("1y-sum"); + downsampler = new Downsampler(source, specification, 0, 0, EST_TIME_ZONE, true); + verify(source, never()).next(); + List values = Lists.newArrayList(); + List timestamps_in_millis = Lists.newArrayList(); + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + values.add(dp.doubleValue()); + timestamps_in_millis.add(dp.timestamp()); + } + + assertEquals(2, values.size()); + timestamp = DateTime.toStartOfYear(BASE_TIME, UTC_TIME_ZONE) - EST_TIME_ZONE.getOffset(BASE_TIME); + for (int i = 0, j = 0; i < values.size(); i++) { + assertEquals((1 << j++) + (1 << j++), values.get(i), 0.0000001); + assertEquals(timestamp, timestamps_in_millis.get(i).longValue()); + timestamp = DateTime.toEndOfYear(timestamp, EST_TIME_ZONE) + 1; + } + } + @Test(expected = UnsupportedOperationException.class) public void testRemove() { new Downsampler(source, THOUSAND_SEC_INTERVAL, AVG).remove(); @@ -373,6 +837,70 @@ public void testSeek() { assertEquals(BASE_TIME + 8600000L, timestamps_in_millis.get(2).longValue()); } + @Test + public void testSeek_useCalendar() { + final DataPoint [] data_points = new DataPoint[4]; + long timestamp = DateTime.toStartOfYear(BASE_TIME, UTC_TIME_ZONE); + final Calendar c = Calendar.getInstance(UTC_TIME_ZONE); + c.setTimeInMillis(timestamp); + for (int i = 0; i < data_points.length; i++) { + long value = 1 << i; + data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); + timestamp = DateTime.toEndOfYear(timestamp, UTC_TIME_ZONE) + 1; + } + + source = spy(SeekableViewsForTest.fromArray(data_points)); + + c.add(Calendar.YEAR, 2); + specification = new DownsamplingSpecification("1y-sum"); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE, + UTC_TIME_ZONE, true); + System.out.println("SEEK: " + c.getTimeInMillis()); + downsampler.seek(c.getTimeInMillis()); + verify(source, never()).next(); + List values = Lists.newArrayList(); + List timestamps_in_millis = Lists.newArrayList(); + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + values.add(dp.doubleValue()); + timestamps_in_millis.add(dp.timestamp()); + } + + assertEquals(2, values.size()); + timestamp = DateTime.toStartOfYear(c.getTimeInMillis(), UTC_TIME_ZONE); + for (int i = 2; i < values.size(); i++) { + assertEquals(1 << i, values.get(i), 0.0000001); + assertEquals(timestamp, timestamps_in_millis.get(i).longValue()); + timestamp = DateTime.toEndOfYear(timestamp, UTC_TIME_ZONE) + 1; + } + + source = spy(SeekableViewsForTest.fromArray(data_points)); + + c.add(Calendar.MILLISECOND, 1); + specification = new DownsamplingSpecification("1y-sum"); + downsampler = new Downsampler(source, specification, 0, 0, UTC_TIME_ZONE, true); + downsampler.seek(c.getTimeInMillis()); + verify(source, never()).next(); + values = Lists.newArrayList(); + timestamps_in_millis = Lists.newArrayList(); + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + values.add(dp.doubleValue()); + timestamps_in_millis.add(dp.timestamp()); + } + + assertEquals(1, values.size()); + timestamp = DateTime.toStartOfYear(c.getTimeInMillis(), UTC_TIME_ZONE); + for (int i = 3; i < values.size(); i++) { + assertEquals(1 << i, values.get(i), 0.0000001); + assertEquals(timestamp, timestamps_in_millis.get(i).longValue()); + timestamp = DateTime.toEndOfYear(timestamp, UTC_TIME_ZONE) + 1; + } + + } + @Test public void testSeek_skipPartialInterval() { downsampler = new Downsampler(source, THOUSAND_SEC_INTERVAL, AVG); diff --git a/test/core/TestFillingDownsampler.java b/test/core/TestFillingDownsampler.java index e762a8d33b..b33dc68fd9 100644 --- a/test/core/TestFillingDownsampler.java +++ b/test/core/TestFillingDownsampler.java @@ -18,6 +18,8 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import java.util.TimeZone; + /** Tests {@link FillingDownsampler}. */ public class TestFillingDownsampler { private static final long BASE_TIME = 1356998400000L; @@ -43,7 +45,7 @@ public void testNaNMissingInterval() { specification = new DownsamplingSpecification("100ms-sum-nan"); final Downsampler downsampler = new FillingDownsampler(source, baseTime, - baseTime + 36 * 25L, specification, 0, 0); + baseTime + 36 * 25L, specification, 0, 0, TimeZone.getDefault(), false); long timestamp = baseTime; step(downsampler, timestamp, Double.NaN); @@ -76,7 +78,7 @@ public void testZeroMissingInterval() { specification = new DownsamplingSpecification("100ms-sum-zero"); final Downsampler downsampler = new FillingDownsampler(source, baseTime, - baseTime + 36 * 25L, specification, 0, 0); + baseTime + 36 * 25L, specification, 0, 0, TimeZone.getDefault(), false); long timestamp = baseTime; step(downsampler, timestamp, 0.); @@ -113,7 +115,7 @@ public void testWithoutMissingIntervals() { specification = new DownsamplingSpecification("100ms-sum-nan"); final Downsampler downsampler = new FillingDownsampler(source, baseTime, - baseTime + 12L * 25L, specification, 0, 0); + baseTime + 12L * 25L, specification, 0, 0, TimeZone.getDefault(), false); long timestamp = baseTime; step(downsampler, timestamp, 42.); @@ -146,7 +148,7 @@ public void testWithOutOfBoundsData() { specification = new DownsamplingSpecification("1m-sum-nan"); final Downsampler downsampler = new FillingDownsampler(source, baseTime, - baseTime + 60000L * 2L, specification, 0, 0); + baseTime + 60000L * 2L, specification, 0, 0, TimeZone.getDefault(), false); long timestamp = 1425335880000L; step(downsampler, timestamp, 30.); @@ -165,7 +167,7 @@ public void testWithOutOfBoundsDataEarly() { specification = new DownsamplingSpecification("1m-sum-nan"); final Downsampler downsampler = new FillingDownsampler(source, baseTime, - baseTime + 60000L * 2L, specification, 0, 0); + baseTime + 60000L * 2L, specification, 0, 0, TimeZone.getDefault(), false); long timestamp = 1425335880000L; step(downsampler, timestamp, Double.NaN); @@ -184,7 +186,7 @@ public void testWithOutOfBoundsDataLate() { specification = new DownsamplingSpecification("1m-sum-nan"); final Downsampler downsampler = new FillingDownsampler(source, baseTime, - baseTime + 60000L * 2L, specification, 0, 0); + baseTime + 60000L * 2L, specification, 0, 0, TimeZone.getDefault(), false); long timestamp = 1425335880000L; step(downsampler, timestamp, Double.NaN); @@ -206,7 +208,7 @@ public void testDownsampler_allFullRange() { specification = new DownsamplingSpecification("0all-sum-nan"); final Downsampler downsampler = new FillingDownsampler(source, BASE_TIME + 5000L,BASE_TIME + 55000L, specification, 0, - Long.MAX_VALUE); + Long.MAX_VALUE, TimeZone.getDefault(), false); step(downsampler, 0, 63); assertFalse(downsampler.hasNext()); @@ -226,7 +228,7 @@ public void testDownsampler_allFilterOnQuery() { specification = new DownsamplingSpecification("0all-sum-nan"); final Downsampler downsampler = new FillingDownsampler(source, BASE_TIME + 5000L,BASE_TIME + 55000L, specification, - BASE_TIME + 15000L, BASE_TIME + 45000L); + BASE_TIME + 15000L, BASE_TIME + 45000L, TimeZone.getDefault(), false); step(downsampler, BASE_TIME + 15000L, 14); assertFalse(downsampler.hasNext()); @@ -246,7 +248,7 @@ public void testDownsampler_allFilterOnQueryOutOfRangeEarly() { specification = new DownsamplingSpecification("0all-sum-nan"); final Downsampler downsampler = new FillingDownsampler(source, BASE_TIME + 5000L,BASE_TIME + 55000L, specification, - BASE_TIME + 65000L, BASE_TIME + 75000L); + BASE_TIME + 65000L, BASE_TIME + 75000L, TimeZone.getDefault(), false); assertFalse(downsampler.hasNext()); } @@ -265,7 +267,7 @@ public void testDownsampler_allFilterOnQueryOutOfRangeLate() { specification = new DownsamplingSpecification("0all-sum-nan"); final Downsampler downsampler = new FillingDownsampler(source, BASE_TIME + 5000L,BASE_TIME + 55000L, specification, - BASE_TIME - 15000L, BASE_TIME - 5000L); + BASE_TIME - 15000L, BASE_TIME - 5000L, TimeZone.getDefault(), false); assertFalse(downsampler.hasNext()); } From 9ae709bded4160cea232383ce56c36ab84a392eb Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sat, 12 Mar 2016 18:06:50 -0800 Subject: [PATCH 136/233] Modify @cpdevoto's downsampling to allow for hourly downsample alignment as well as align to useful boundaries based on the start of calendar events such as the start of the year or start of the month instead of basing it off the first data point timestamp. Also modify the API calls so they use the downsample specification instead of adding params to the functions. And squash a bunch of the code to improve Calendar performance a bit and make the UTs a little simpler. Signed-off-by: Chris Larsen --- src/core/AggregationIterator.java | 5 +- src/core/Downsampler.java | 214 ++--- src/core/DownsamplingSpecification.java | 53 +- src/core/FillingDownsampler.java | 64 +- src/core/Query.java | 18 - src/core/Span.java | 9 +- src/core/SpanGroup.java | 12 +- src/core/TSQuery.java | 26 +- src/core/TsdbQuery.java | 44 - src/utils/DateTime.java | 403 +++++---- test/core/SeekableViewsForTest.java | 6 +- test/core/TestDownsampler.java | 867 +++++++++++++------ test/core/TestDownsamplingSpecification.java | 130 ++- test/core/TestFillingDownsampler.java | 564 +++++++++++- test/core/TestTSQuery.java | 65 ++ test/utils/TestDateTime.java | 488 +++++++++++ 16 files changed, 2288 insertions(+), 680 deletions(-) diff --git a/src/core/AggregationIterator.java b/src/core/AggregationIterator.java index 64e9179f18..03bc93b8cf 100644 --- a/src/core/AggregationIterator.java +++ b/src/core/AggregationIterator.java @@ -15,7 +15,6 @@ import java.util.Arrays; import java.util.List; import java.util.NoSuchElementException; -import java.util.TimeZone; import com.google.common.annotations.VisibleForTesting; @@ -305,8 +304,6 @@ public static AggregationIterator create(final List spans, final DownsamplingSpecification downsampler, final long query_start, final long query_end, - final TimeZone timezone, - final boolean use_calendar, final boolean rate, final RateOptions rate_options) { final int size = spans.size(); @@ -318,7 +315,7 @@ public static AggregationIterator create(final List spans, it = spans.get(i).spanIterator(); } else { it = spans.get(i).downsampler(start_time, end_time, downsampler, - query_start, query_end, timezone, use_calendar); + query_start, query_end); } if (rate) { it = new RateSpan(it, rate_options); diff --git a/src/core/Downsampler.java b/src/core/Downsampler.java index 8123cb5579..004e768e33 100644 --- a/src/core/Downsampler.java +++ b/src/core/Downsampler.java @@ -12,8 +12,8 @@ // see . package net.opentsdb.core; +import java.util.Calendar; import java.util.NoSuchElementException; -import java.util.TimeZone; import net.opentsdb.utils.DateTime; @@ -21,11 +21,11 @@ * Iterator that downsamples data points using an {@link Aggregator}. */ public class Downsampler implements SeekableView, DataPoint { - - static final long ONE_WEEK_INTERVAL = 604800000L; - static final long ONE_MONTH_INTERVAL = 2592000000L; - static final long ONE_YEAR_INTERVAL = 31536000000L; - static final long ONE_DAY_INTERVAL = 86400000L; + + /** Matches the weekly downsampler as it requires special handling. */ + protected final static int WEEK_UNIT = DateTime.unitsToCalendarType("w"); + protected final static int DAY_UNIT = DateTime.unitsToCalendarType("d"); + protected final static int WEEK_LENGTH = 7; /** The downsampling specification when provided */ protected final DownsamplingSpecification specification; @@ -51,8 +51,11 @@ public class Downsampler implements SeekableView, DataPoint { /** Whether or not to merge all DPs in the source into one vaalue */ protected final boolean run_all; - protected final TimeZone timezone; - protected final boolean use_calendar; + /** The interval to use with a calendar */ + protected final int interval; + + /** The unit to use with a calendar as a Calendar integer */ + protected final int unit; /** * Ctor. @@ -66,18 +69,17 @@ public class Downsampler implements SeekableView, DataPoint { final long interval_ms, final Aggregator downsampler) { this.source = source; - values_in_interval = new ValuesInInterval(); if (downsampler == Aggregators.NONE) { throw new IllegalArgumentException("cannot use the NONE " + "aggregator for downsampling"); } specification = new DownsamplingSpecification(interval_ms, downsampler, DownsamplingSpecification.DEFAULT_FILL_POLICY); + values_in_interval = new ValuesInInterval(); query_start = 0; query_end = 0; + interval = unit = 0; run_all = false; - timezone = TimeZone.getDefault(); - use_calendar = false; } /** @@ -91,23 +93,30 @@ public class Downsampler implements SeekableView, DataPoint { Downsampler(final SeekableView source, final DownsamplingSpecification specification, final long query_start, - final long query_end, - final TimeZone timezone, - final boolean use_calendar + final long query_end ) { this.source = source; this.specification = specification; values_in_interval = new ValuesInInterval(); this.query_start = query_start; this.query_end = query_end; - this.timezone = timezone; - this.use_calendar = use_calendar; final String s = specification.getStringInterval(); if (s != null && s.toLowerCase().contains("all")) { run_all = true; - } else { + interval = unit = 0; + } else if (s != null && specification.useCalendar()) { + if (s.toLowerCase().contains("ms")) { + interval = Integer.parseInt(s.substring(0, s.length() - 2)); + unit = DateTime.unitsToCalendarType(s.substring(s.length() - 2)); + } else { + interval = Integer.parseInt(s.substring(0, s.length() - 1)); + unit = DateTime.unitsToCalendarType(s.substring(s.length() - 1)); + } + run_all = false; + } else { run_all = false; + interval = unit = 0; } } @@ -197,6 +206,12 @@ public String toString() { /** Iterates source values for an interval. */ protected class ValuesInInterval implements Aggregator.Doubles { + /** An optional calendar set to the current timestamp for the data point */ + private Calendar previous_calendar; + + /** An optional calendar set to the end of the interval timestamp */ + private Calendar next_calendar; + /** The end of the current interval. */ private long timestamp_end_interval = Long.MIN_VALUE; @@ -215,6 +230,8 @@ protected class ValuesInInterval implements Aggregator.Doubles { protected ValuesInInterval() { if (run_all) { timestamp_end_interval = query_end; + } else if (!specification.useCalendar()) { + timestamp_end_interval = specification.getInterval(); } } @@ -225,9 +242,25 @@ protected void initializeIfNotDone() { // performance penalty by accessing the unnecessary first data of a span. if (!initialized) { initialized = true; - moveToNextValue(); - if (!run_all) { - resetEndOfInterval(); + if (source.hasNext()) { + moveToNextValue(); + if (!run_all) { + if (specification.useCalendar()) { + previous_calendar = DateTime.previousInterval(next_dp.timestamp(), + interval, unit, specification.getTimezone()); + next_calendar = DateTime.previousInterval(next_dp.timestamp(), + interval, unit, specification.getTimezone()); + if (unit == WEEK_UNIT) { + next_calendar.add(DAY_UNIT, interval * WEEK_LENGTH); + } else { + next_calendar.add(unit, interval); + } + timestamp_end_interval = next_calendar.getTimeInMillis(); + } else { + timestamp_end_interval = alignTimestamp(next_dp.timestamp()) + + specification.getInterval(); + } + } } } } @@ -266,10 +299,18 @@ private void moveToNextValue() { * interval. */ private void resetEndOfInterval() { if (has_next_value_from_source && !run_all) { - if (use_calendar && isCalendarInterval()) { - timestamp_end_interval = toEndOfInterval(next_dp.timestamp()); - } else { - // Sets the end of the interval of the timestamp. + if (specification.useCalendar()) { + while (next_dp.timestamp() >= timestamp_end_interval) { + if (unit == WEEK_UNIT) { + previous_calendar.add(DAY_UNIT, interval * WEEK_LENGTH); + next_calendar.add(DAY_UNIT, interval * WEEK_LENGTH); + } else { + previous_calendar.add(unit, interval); + next_calendar.add(unit, interval); + } + timestamp_end_interval = next_calendar.getTimeInMillis(); + } + } else { timestamp_end_interval = alignTimestamp(next_dp.timestamp()) + specification.getInterval(); } @@ -290,10 +331,18 @@ void seekInterval(final long timestamp) { // timestamp.. if (run_all) { source.seek(timestamp); - } else if (use_calendar && isCalendarInterval()) { - source.seek(alignTimestamp(timestamp + toEndOfInterval(timestamp) - - toStartOfInterval(timestamp))); - } else { + } else if (specification.useCalendar()) { + final Calendar seek_calendar = DateTime.previousInterval( + timestamp, interval, unit, specification.getTimezone()); + if (timestamp > seek_calendar.getTimeInMillis()) { + if (unit == WEEK_UNIT) { + seek_calendar.add(DAY_UNIT, interval * WEEK_LENGTH); + } else { + seek_calendar.add(unit, interval); + } + } + source.seek(seek_calendar.getTimeInMillis()); + } else { source.seek(alignTimestamp(timestamp + specification.getInterval() - 1)); } initialized = false; @@ -306,9 +355,9 @@ protected long getIntervalTimestamp() { // provides the correct context for seek. if (run_all) { return timestamp_end_interval; - } else if (use_calendar && isCalendarInterval()) { - return toStartOfInterval(timestamp_end_interval); - } else { + } else if (specification.useCalendar()) { + return previous_calendar.getTimeInMillis(); + } else { return alignTimestamp(timestamp_end_interval - specification.getInterval()); } @@ -316,102 +365,7 @@ protected long getIntervalTimestamp() { /** Returns timestamp aligned by interval. */ protected long alignTimestamp(final long timestamp) { - if (use_calendar && isCalendarInterval()) { - return toStartOfInterval(timestamp); - } else { - return timestamp - (timestamp % specification.getInterval()); - } - } - - /** Returns a flag denoting whether the interval can - * be aligned to the calendar */ - private boolean isCalendarInterval () { - if (specification.getInterval() != 0 && - (specification.getInterval() % ONE_YEAR_INTERVAL == 0 || - specification.getInterval() % ONE_MONTH_INTERVAL == 0 || - specification.getInterval() % ONE_WEEK_INTERVAL == 0 || - specification.getInterval() % ONE_DAY_INTERVAL == 0)) { - return true; - } - return false; - } - - /** Returns a timestamp corresponding to the start of the interval - * in which the specified timestamp occurs, aligned to the calendar - * based on the timezone. */ - private long toStartOfInterval(long timestamp) { - if (specification.getInterval() % ONE_YEAR_INTERVAL == 0) { - final long multiplier = specification.getInterval() / ONE_YEAR_INTERVAL; - long result = timestamp; - for (long i = 0; i < multiplier; i++) { - result = DateTime.toStartOfYear(result, timezone) - 1; - } - return result + 1; - } else if (specification.getInterval() % ONE_MONTH_INTERVAL == 0) { - final long multiplier = specification.getInterval() / ONE_MONTH_INTERVAL; - long result = timestamp; - for (long i = 0; i < multiplier; i++) { - result = DateTime.toStartOfMonth(result, timezone) - 1; - } - return result + 1; - } else if (specification.getInterval() % ONE_WEEK_INTERVAL == 0) { - final long multiplier = specification.getInterval() / ONE_WEEK_INTERVAL; - long result = timestamp; - for (long i = 0; i < multiplier; i++) { - result = DateTime.toStartOfWeek(result, timezone) - 1; - } - return result + 1; - } else if (specification.getInterval() % ONE_DAY_INTERVAL == 0) { - final long multiplier = specification.getInterval() / ONE_DAY_INTERVAL; - long result = timestamp; - for (long i = 0; i < multiplier; i++) { - result = DateTime.toStartOfDay(result, timezone) - 1; - } - return result + 1; - } else { - throw new IllegalArgumentException(specification.getInterval() + - " does not correspond to a " + - "an interval that can be aligned to the calendar."); - } - } - - /** Returns a timestamp corresponding to the end of the interval - * in which the specified timestamp occurs, aligned to the calendar - * based on the timezone. */ - private long toEndOfInterval(long timestamp) { - if (specification.getInterval() % ONE_YEAR_INTERVAL == 0) { - final long multiplier = specification.getInterval() / ONE_YEAR_INTERVAL; - long result = timestamp; - for (long i = 0; i < multiplier; i++) { - result = DateTime.toEndOfYear(result, timezone) + 1; - } - return result - 1; - } else if (specification.getInterval() % ONE_MONTH_INTERVAL == 0) { - final long multiplier = specification.getInterval() / ONE_MONTH_INTERVAL; - long result = timestamp; - for (long i = 0; i < multiplier; i++) { - result = DateTime.toEndOfMonth(result, timezone) + 1; - } - return result - 1; - } else if (specification.getInterval() % ONE_WEEK_INTERVAL == 0) { - final long multiplier = specification.getInterval() / ONE_WEEK_INTERVAL; - long result = timestamp; - for (long i = 0; i < multiplier; i++) { - result = DateTime.toEndOfWeek(result, timezone) + 1; - } - return result - 1; - } else if (specification.getInterval() % ONE_DAY_INTERVAL == 0) { - final long multiplier = specification.getInterval() / ONE_DAY_INTERVAL; - long result = timestamp; - for (long i = 0; i < multiplier; i++) { - result = DateTime.toEndOfDay(result, timezone) + 1; - } - return result - 1; - } else { - throw new IllegalArgumentException(specification.getInterval() + - " does not correspond to a " + - "an interval that can be aligned to the calendar."); - } + return timestamp - (timestamp % specification.getInterval()); } // ---------------------- // @@ -445,7 +399,11 @@ public String toString() { buf.append("ValuesInInterval: ") .append(", timestamp_end_interval=").append(timestamp_end_interval) .append(", has_next_value_from_source=") - .append(has_next_value_from_source); + .append(has_next_value_from_source) + .append(", previousCalendar=") + .append(previous_calendar == null ? "null" : previous_calendar) + .append(", nextCalendar=") + .append(next_calendar == null ? "null" : next_calendar); if (has_next_value_from_source) { buf.append(", nextValue=(").append(next_dp).append(')'); } diff --git a/src/core/DownsamplingSpecification.java b/src/core/DownsamplingSpecification.java index 5abe810029..b64cceb157 100644 --- a/src/core/DownsamplingSpecification.java +++ b/src/core/DownsamplingSpecification.java @@ -13,6 +13,7 @@ package net.opentsdb.core; import java.util.NoSuchElementException; +import java.util.TimeZone; import com.google.common.base.MoreObjects; import net.opentsdb.utils.DateTime; @@ -40,12 +41,18 @@ public final class DownsamplingSpecification { //The string interval, e.g. 1h, 30d, etc private final String string_interval; - + // Parsed downsampler function. private final Aggregator function; // Parsed fill policy: whether to interpolate or to fill. private final FillPolicy fill_policy; + + // Whether or not to use the calendar for intervals + private boolean use_calendar; + + // The user provided timezone for calendar alignment (defaults to UTC) + private TimeZone timezone; /** * A specification indicating no downsampling is requested. @@ -55,6 +62,8 @@ private DownsamplingSpecification() { function = NO_FUNCTION; fill_policy = DEFAULT_FILL_POLICY; string_interval = null; + use_calendar = false; + timezone = DateTime.timezones.get(DateTime.UTC_ID); } /** @@ -85,12 +94,16 @@ public DownsamplingSpecification(final long interval, this.function = function; this.fill_policy = fill_policy; string_interval = null; + use_calendar = false; + timezone = DateTime.timezones.get(DateTime.UTC_ID); } /** * C-tor for string representations. * The argument to this c-tor should have the following format: * {@code interval-function[-fill_policy]}. + * This ctor supports the "all" flag to downsample to a single value as well + * as units suffixed with 'c' to use the calendar for downsample alignment. * @param specification String representation of a downsample specifier. * @throws IllegalArgumentException if the specification is null or invalid. */ @@ -118,9 +131,16 @@ public DownsamplingSpecification(final String specification) { // This will throw if interval is invalid. if (parts[0].contains("all")) { interval = NO_INTERVAL; + use_calendar = false; string_interval = parts[0]; + } else if (parts[0].charAt(parts[0].length() - 1) == 'c') { + final String duration = parts[0].substring(0, parts[0].length() - 1); + interval = DateTime.parseDuration(duration); + string_interval = duration; + use_calendar = true; } else { interval = DateTime.parseDuration(parts[0]); + use_calendar = false; string_interval = parts[0]; } @@ -155,8 +175,25 @@ public DownsamplingSpecification(final String specification) { // Default to linear interpolation. fill_policy = FillPolicy.NONE; } + timezone = DateTime.timezones.get(DateTime.UTC_ID); } + /** @param use_calendar Whether or not to use the calendar when downsampling + * @since 2.3 */ + public void setUseCalendar(final boolean use_calendar) { + this.use_calendar = use_calendar; + } + + /** @param timezone The timezone to use when downsampling on calendar + * boundaries. + * @since 2.3 */ + public void setTimezone(final TimeZone timezone) { + if (timezone == null) { + throw new IllegalArgumentException("Timezone cannot be null"); + } + this.timezone = timezone; + } + /** * Get the downsampling interval, in milliseconds. * @return the downsampling interval, in milliseconds. @@ -187,6 +224,18 @@ public FillPolicy getFillPolicy() { return fill_policy; } + /** @return Whether or not to use the calendar when downsampling + * @since 2.3 */ + public boolean useCalendar() { + return use_calendar; + } + + /** @return The timezone to use when downsampling on calendar boundaries. + * @since 2.3 */ + public TimeZone getTimezone() { + return timezone; + } + @Override public String toString() { return MoreObjects.toStringHelper(this) @@ -194,6 +243,8 @@ public String toString() { .add("function", getFunction()) .add("fillPolicy", getFillPolicy()) .add("stringInterval", string_interval) + .add("useCalendar", useCalendar()) + .add("timeZone", getTimezone() != null ? getTimezone().getID() : null) .toString(); } } diff --git a/src/core/FillingDownsampler.java b/src/core/FillingDownsampler.java index 2de42bb734..bd47205bf8 100644 --- a/src/core/FillingDownsampler.java +++ b/src/core/FillingDownsampler.java @@ -12,8 +12,10 @@ // see . package net.opentsdb.core; +import java.util.Calendar; import java.util.NoSuchElementException; -import java.util.TimeZone; + +import net.opentsdb.utils.DateTime; /** * A specialized downsampler that returns special values, based on the fill @@ -26,6 +28,12 @@ public class FillingDownsampler extends Downsampler { /** Track when the downsampled data should end. */ protected long end_timestamp; + + /** An optional calendar set to the current timestamp for the data point */ + private final Calendar previous_calendar; + + /** An optional calendar set to the end of the interval timestamp */ + private final Calendar next_calendar; /** * Create a new nulling downsampler. @@ -45,7 +53,7 @@ public class FillingDownsampler extends Downsampler { final Aggregator downsampler, final FillPolicy fill_policy) { this(source, start_time, end_time, new DownsamplingSpecification(interval_ms, downsampler, fill_policy) - , 0, 0, TimeZone.getDefault(), false); + , 0, 0); } /** @@ -61,11 +69,9 @@ public class FillingDownsampler extends Downsampler { */ FillingDownsampler(final SeekableView source, final long start_time, final long end_time, final DownsamplingSpecification specification, - final long query_start, final long end_start, - final TimeZone timezone, - final boolean use_calendar) { + final long query_start, final long end_start) { // Lean on the superclass implementation. - super(source, specification, query_start, end_start, timezone, use_calendar); + super(source, specification, query_start, end_start); // Ensure we aren't given a bogus fill policy. if (FillPolicy.NONE == specification.getFillPolicy()) { @@ -78,11 +84,36 @@ public class FillingDownsampler extends Downsampler { if (run_all) { timestamp = start_time; end_timestamp = end_time; + previous_calendar = next_calendar = null; + } else if (specification.useCalendar()) { + previous_calendar = DateTime.previousInterval(start_time, interval, unit, + specification.getTimezone()); + if (unit == WEEK_UNIT) { + previous_calendar.add(DAY_UNIT, -(interval * WEEK_LENGTH)); + } else { + previous_calendar.add(unit, -interval); + } + next_calendar = DateTime.previousInterval(start_time, interval, unit, + specification.getTimezone()); + + final Calendar end_calendar = DateTime.previousInterval( + end_time, interval, unit, specification.getTimezone()); + if (end_calendar.getTimeInMillis() == next_calendar.getTimeInMillis()) { + // advance once + if (unit == WEEK_UNIT) { + end_calendar.add(DAY_UNIT, interval * WEEK_LENGTH); + } else { + end_calendar.add(unit, interval); + } + } + timestamp = next_calendar.getTimeInMillis(); + end_timestamp = end_calendar.getTimeInMillis(); } else { // Use the values-in-interval object to align the timestamps at which we // expect data to arrive for the first and last intervals. timestamp = values_in_interval.alignTimestamp(start_time); end_timestamp = values_in_interval.alignTimestamp(end_time); + previous_calendar = next_calendar = null; } } @@ -120,7 +151,9 @@ public DataPoint next() { values_in_interval.initializeIfNotDone(); // Skip any leading data outside the query bounds. - long actual = values_in_interval.getIntervalTimestamp(); + long actual = values_in_interval.hasNextValue() ? + values_in_interval.getIntervalTimestamp() : Long.MAX_VALUE; + while (!run_all && values_in_interval.hasNextValue() && actual < timestamp) { // The actual timestamp precedes our expected, so there's data in the @@ -157,7 +190,20 @@ public DataPoint next() { } // Advance the expected timestamp to the next interval. - timestamp += specification.getInterval(); + if (!run_all) { + if (specification.useCalendar()) { + if (unit == WEEK_UNIT) { + previous_calendar.add(DAY_UNIT, interval * WEEK_LENGTH); + next_calendar.add(DAY_UNIT, interval * WEEK_LENGTH); + } else { + previous_calendar.add(unit, interval); + next_calendar.add(unit, interval); + } + timestamp = next_calendar.getTimeInMillis(); + } else { + timestamp += specification.getInterval(); + } + } // This object also represents the data. return this; @@ -172,6 +218,8 @@ public DataPoint next() { public long timestamp() { if (run_all) { return query_start; + } else if (specification.useCalendar()) { + return previous_calendar.getTimeInMillis(); } return timestamp - specification.getInterval(); } diff --git a/src/core/Query.java b/src/core/Query.java index 2d1db08f83..f4ea1bce4f 100644 --- a/src/core/Query.java +++ b/src/core/Query.java @@ -66,24 +66,6 @@ public interface Query { * @return A strictly positive integer. */ long getEndTime(); - - /** - * Sets the timezone to use for aligning intervals based on the calendar. - * @param timezone the timezone to use - */ - void setTimezone(String timezone); - - /** @return the timezone to use for aligning intervals based on the calendar. */ - String getTimezone(); - - /** - * Sets a flag denoting whether or not to align intervals based on the calendar. - * @param use_calendar true, if the intervals should be aligned based on the calendar; false, otherwise - */ - void setUseCalendar(boolean use_calendar); - - /** @return A flag denoting whether or not to align intervals based on the calendar. */ - boolean getUseCalendar(); /** * Sets whether or not the data queried will be deleted. diff --git a/src/core/Span.java b/src/core/Span.java index a3d9d4894e..15338956ae 100644 --- a/src/core/Span.java +++ b/src/core/Span.java @@ -18,7 +18,6 @@ import java.util.List; import java.util.Map; import java.util.NoSuchElementException; -import java.util.TimeZone; import net.opentsdb.meta.Annotation; import net.opentsdb.uid.UniqueId; @@ -500,18 +499,16 @@ Downsampler downsampler(final long start_time, final long end_time, final DownsamplingSpecification downsampler, final long query_start, - final long query_end, - final TimeZone timezone, - final boolean use_calendar) { + final long query_end) { if (downsampler == null) { return null; } if (FillPolicy.NONE == downsampler.getFillPolicy()) { return new Downsampler(spanIterator(), downsampler, - query_start, query_end, timezone, use_calendar); + query_start, query_end); } return new FillingDownsampler(spanIterator(), start_time, end_time, - downsampler, query_start, query_end, timezone, use_calendar); + downsampler, query_start, query_end); } public int getQueryIndex() { diff --git a/src/core/SpanGroup.java b/src/core/SpanGroup.java index a24eb1e369..7e50bb8ef6 100644 --- a/src/core/SpanGroup.java +++ b/src/core/SpanGroup.java @@ -20,7 +20,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.TimeZone; import org.hbase.async.Bytes; import org.hbase.async.Bytes.ByteMap; @@ -107,9 +106,6 @@ final class SpanGroup implements DataPoints { /** The TSDB to which we belong, used for resolution */ private final TSDB tsdb; - private final TimeZone timezone; - private final boolean use_calendar; - /** * Ctor. * @param tsdb The TSDB we belong to. @@ -197,7 +193,7 @@ final class SpanGroup implements DataPoints { downsampler != null ? new DownsamplingSpecification(interval, downsampler, fill_policy) : null, - 0, 0, TimeZone.getDefault(), false, query_index); + 0, 0, query_index); } /** @@ -229,8 +225,6 @@ final class SpanGroup implements DataPoints { final DownsamplingSpecification downsampler, final long query_start, final long query_end, - final TimeZone timezone, - final boolean use_calendar, final int query_index) { annotations = new ArrayList(); this.start_time = (start_time & Const.SECOND_MASK) == 0 ? @@ -250,8 +244,6 @@ final class SpanGroup implements DataPoints { this.query_end = query_end; this.query_index = query_index; this.tsdb = tsdb; - this.timezone = timezone; - this.use_calendar = use_calendar; } /** @@ -495,7 +487,7 @@ public SeekableView iterator() { return AggregationIterator.create(spans, start_time, end_time, aggregator, aggregator.interpolationMethod(), downsampler, query_start, query_end, - timezone, use_calendar, rate, rate_options); + rate, rate_options); } /** diff --git a/src/core/TSQuery.java b/src/core/TSQuery.java index 80a4a11fc0..071b401812 100644 --- a/src/core/TSQuery.java +++ b/src/core/TSQuery.java @@ -16,6 +16,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.TimeZone; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -113,7 +114,7 @@ public int hashCode() { // NOTE: Do not add any non-user submitted variables to the hash. We don't // want the hash to change after validation. // We also don't care about stats or summary - return Objects.hashCode(start, end, timezone, options, padding, + return Objects.hashCode(start, end, timezone, use_calendar, options, padding, no_annotations, with_global_annotations, show_tsuids, queries, ms_resolution); } @@ -137,6 +138,7 @@ public boolean equals(final Object obj) { return Objects.equal(start, query.start) && Objects.equal(end, query.end) && Objects.equal(timezone, query.timezone) + && Objects.equal(use_calendar,query.use_calendar) && Objects.equal(options, query.options) && Objects.equal(padding, query.padding) && Objects.equal(no_annotations, query.no_annotations) @@ -182,6 +184,21 @@ public void validateAndSetQuery() { int i = 0; for (TSSubQuery sub : queries) { sub.validateAndSetQuery(); + final DownsamplingSpecification ds = sub.downsamplingSpecification(); + if (ds != null && timezone != null && !timezone.isEmpty() && + ds != DownsamplingSpecification.NO_DOWNSAMPLER) { + final TimeZone tz = DateTime.timezones.get(timezone); + if (tz == null) { + throw new IllegalArgumentException( + "The timezone specification could not be found"); + } + ds.setTimezone(tz); + } + if (ds != null && use_calendar && + ds != DownsamplingSpecification.NO_DOWNSAMPLER) { + ds.setUseCalendar(true); + } + sub.setIndex(i++); } } @@ -365,7 +382,9 @@ public boolean getDelete() { return this.delete; } - /** @return the flag denoting whether intervals should be aligned based on the calendar */ + /** @return the flag denoting whether intervals should be aligned based on + * the calendar + * @since 2.3 */ public boolean getUseCalendar() { return use_calendar; } @@ -455,7 +474,8 @@ public void setDelete(boolean delete) { this.delete = delete; } - /** @param use_calendar a flag denoting whether or not to align intervals based on the calendar */ + /** @param use_calendar a flag denoting whether or not to align intervals + * based on the calendar @since 2.3 */ public void setUseCalendar(boolean use_calendar) { this.use_calendar = use_calendar; } diff --git a/src/core/TsdbQuery.java b/src/core/TsdbQuery.java index 5282dd4f8c..105cecf8c2 100644 --- a/src/core/TsdbQuery.java +++ b/src/core/TsdbQuery.java @@ -22,7 +22,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.TimeZone; import java.util.TreeMap; import org.slf4j.Logger; @@ -132,12 +131,6 @@ final class TsdbQuery implements Query { /** Tag value filters to apply post scan */ private List filters; - /** The timezone to use for aligning intervals based on the calendar */ - private String timezone; - - /** A flag denoting whether or not to align intervals based on the calendar */ - private boolean use_calendar; - /** An object for storing stats in regarding the query. May be null */ private QueryStats query_stats; @@ -310,35 +303,6 @@ public void setTimeSeries(final List tsuids, this.rate_options = rate_options; } - /** - * Sets the timezone to use for aligning intervals based on the calendar. - * @param timezone the timezone to use - */ - public void setTimezone(String timezone) { - this.timezone = timezone; - } - - /** @return the timezone to use for aligning intervals based on the calendar. */ - @Override - public String getTimezone() { - return this.timezone; - } - - /** - * Sets a flag denoting whether or not to align intervals based on the calendar. - * @param use_calendar true, if the intervals should be aligned based on the calendar; false, otherwise - */ - @Override - public void setUseCalendar(boolean use_calendar) { - this.use_calendar = use_calendar; - } - - /** @return A flag denoting whether or not to align intervals based on the calendar. */ - @Override - public boolean getUseCalendar() { - return this.use_calendar; - } - /** * @param explicit_tags Whether or not to match only on the given tags * @since 2.3 @@ -361,8 +325,6 @@ public Deferred configureFromQuery(final TSQuery query, setStartTime(query.startTime()); setEndTime(query.endTime()); setDelete(query.getDelete()); - setTimezone(query.getTimezone()); - setUseCalendar(query.getUseCalendar()); query_index = index; query_stats = query.getQueryStats(); @@ -891,8 +853,6 @@ public DataPoints[] call(final TreeMap spans) throws Exception { downsampler, getStartTime(), getEndTime(), - timezone != null ? DateTime.timezones.get(timezone) : TimeZone.getDefault(), - use_calendar, query_index); group.add(span); groups[i++] = group; @@ -912,8 +872,6 @@ public DataPoints[] call(final TreeMap spans) throws Exception { downsampler, getStartTime(), getEndTime(), - timezone != null ? DateTime.timezones.get(timezone) : TimeZone.getDefault(), - use_calendar, query_index); if (query_stats != null) { query_stats.addStat(query_index, QueryStat.GROUP_BY_TIME, 0); @@ -964,8 +922,6 @@ public DataPoints[] call(final TreeMap spans) throws Exception { downsampler, getStartTime(), getEndTime(), - timezone != null ? DateTime.timezones.get(timezone) : TimeZone.getDefault(), - use_calendar, query_index); // Copy the array because we're going to keep `group' and overwrite // its contents. So we want the collection to have an immutable copy. diff --git a/src/utils/DateTime.java b/src/utils/DateTime.java index d1386a45fa..6690573ddc 100644 --- a/src/utils/DateTime.java +++ b/src/utils/DateTime.java @@ -27,7 +27,9 @@ * @since 2.0 */ public class DateTime { - + /** ID of the UTC timezone */ + public static final String UTC_ID = "UTC"; + /** * Immutable cache mapping a timezone name to its object. * We do this because the JDK's TimeZone class was implemented by retards, @@ -171,6 +173,10 @@ public static final long parseDuration(final String duration) { int unit = 0; while (Character.isDigit(duration.charAt(unit))) { unit++; + if (unit >= duration.length()) { + throw new IllegalArgumentException("Invalid duration, must have an " + + "integer and unit: " + duration); + } } try { interval = Long.parseLong(duration.substring(0, unit)); @@ -306,175 +312,254 @@ public static double msFromNanoDiff(final long end, final long start) { } return ((double) end - (double) start) / 1000000; } - - /** - * Returns a timestamp corresponding the beginning of the year in which the specified - * timestamp occurs. This operation is performed based on the specified time zone. - * @param timestamp the epoch time - * @param time_zone the time zone used to determine the beginning of the year - * @return the epoch time corresponding to the beginning of the year - */ - public static long toStartOfYear(final long timestamp, final TimeZone time_zone) { - final Calendar c = getStartOfYear(timestamp, time_zone); - return c.getTimeInMillis(); - } - - /** - * Returns a timestamp corresponding the end of the year in which the specified - * timestamp occurs. This operation is performed based on the specified time zone. - * @param timestamp the epoch time - * @param time_zone the time zone used to determine the end of the year - * @return the epoch time corresponding to the end of the year - */ - public static long toEndOfYear(final long timestamp, final TimeZone time_zone) { - final Calendar c = getStartOfYear(timestamp, time_zone); - c.add(Calendar.YEAR, 1); - return c.getTimeInMillis() - 1; - } - - /** - * Returns a timestamp corresponding the beginning of the month in which the specified - * timestamp occurs. This operation is performed based on the specified time zone. - * @param timestamp the epoch time - * @param time_zone the time zone used to determine the beginning of the month - * @return the epoch time corresponding to the beginning of the month - */ - public static long toStartOfMonth(final long timestamp, final TimeZone time_zone) { - final Calendar c = getStartOfMonth(timestamp, time_zone); - return c.getTimeInMillis(); - } /** - * Returns a timestamp corresponding the end of the month in which the specified - * timestamp occurs. This operation is performed based on the specified time zone. - * @param timestamp the epoch time - * @param time_zone the time zone used to determine the end of the month - * @return the epoch time corresponding to the end of the month + * Returns a calendar set to the previous interval time based on the + * units and UTC the timezone. This allows for snapping to day, week, + * monthly, etc. boundaries. + * NOTE: It uses a calendar for snapping so isn't as efficient as a simple + * modulo calculation. + * NOTE: For intervals that don't nicely divide into their given unit (e.g. + * a 23s interval where 60 seconds is not divisible by 23) the base time may + * start at the top of the day (for ms and s) or from Unix epoch 0. In the + * latter case, setting up the base timestamp may be slow if the caller does + * something silly like "23m" where we iterate 23 minutes at a time from 0 + * till we find the proper timestamp. + * TODO - There is likely a better way to do all of this + * @param ts The timestamp to find an interval for, in milliseconds as + * a Unix epoch. + * @param interval The interval as a measure of units. + * @param unit The unit. This must cast to a Calendar time unit. + * @return A calendar set to the timestamp aligned to the proper interval + * before the given ts + * @throws IllegalArgumentException if the timestamp is negative, if the + * interval is less than 1 or the unit is unrecognized. + * @since 2.3 */ - public static long toEndOfMonth(final long timestamp, final TimeZone time_zone) { - final Calendar c = getStartOfMonth(timestamp, time_zone); - c.add(Calendar.MONTH, 1); - return c.getTimeInMillis() - 1; - } - - /** - * Returns a timestamp corresponding the beginning of the week in which the specified - * timestamp occurs. This operation is performed based on the specified time zone. - * @param timestamp the epoch time - * @param time_zone the time zone used to determine the beginning of the week - * @return the epoch time corresponding to the beginning of the week - */ - public static long toStartOfWeek(final long timestamp, final TimeZone time_zone) { - final Calendar c = getStartOfWeek(timestamp, time_zone); - return c.getTimeInMillis(); - } - - /** - * Returns a timestamp corresponding the end of the week in which the specified - * timestamp occurs. This operation is performed based on the specified time zone. - * @param timestamp the epoch time - * @param time_zone the time zone used to determine the end of the week - * @return the epoch time corresponding to the end of the week - */ - public static long toEndOfWeek(final long timestamp, final TimeZone time_zone) { - final Calendar c = getStartOfWeek(timestamp, time_zone); - c.add(Calendar.DATE, 7); - return c.getTimeInMillis() - 1; - } - - /** - * Returns a timestamp corresponding the beginning of the day in which the specified - * timestamp occurs. This operation is performed based on the specified time zone. - * @param timestamp the epoch time - * @param time_zone the time zone used to determine the beginning of the day - * @return the epoch time corresponding to the beginning of the day - */ - public static long toStartOfDay(final long timestamp, final TimeZone time_zone) { - final Calendar c = getStartOfDay(timestamp, time_zone); - return c.getTimeInMillis(); + public static Calendar previousInterval(final long ts, final int interval, + final int unit) { + return previousInterval(ts, interval, unit, null); } /** - * Returns a timestamp corresponding the end of the day in which the specified - * timestamp occurs. This operation is performed based on the specified time zone. - * @param timestamp the epoch time - * @param time_zone the time zone used to determine the end of the day - * @return the epoch time corresponding to the end of the day - */ - public static long toEndOfDay(final long timestamp, final TimeZone time_zone) { - final Calendar c = getStartOfDay(timestamp, time_zone); - c.add(Calendar.DATE, 1); - return c.getTimeInMillis() - 1; - } - - /** - * Returns a Calendar object corresponding the beginning of the year in which the specified - * timestamp occurs. This operation is performed based on the specified time zone. - * @param timestamp the epoch time - * @param time_zone the time zone used to determine the beginning of the year - * @return a Calendar object corresponding to the beginning of the year + * Returns a calendar set to the previous interval time based on the + * units and timezone. This allows for snapping to day, week, monthly, etc. + * boundaries. + * NOTE: It uses a calendar for snapping so isn't as efficient as a simple + * modulo calculation. + * NOTE: For intervals that don't nicely divide into their given unit (e.g. + * a 23s interval where 60 seconds is not divisible by 23) the base time may + * start at the top of the day (for ms and s) or from Unix epoch 0. In the + * latter case, setting up the base timestamp may be slow if the caller does + * something silly like "23m" where we iterate 23 minutes at a time from 0 + * till we find the proper timestamp. + * TODO - There is likely a better way to do all of this + * @param ts The timestamp to find an interval for, in milliseconds as + * a Unix epoch. + * @param interval The interval as a measure of units. + * @param unit The unit. This must cast to a Calendar time unit. + * @param tz An optional timezone. + * @return A calendar set to the timestamp aligned to the proper interval + * before the given ts + * @throws IllegalArgumentException if the timestamp is negative, if the + * interval is less than 1 or the unit is unrecognized. + * @since 2.3 */ - private static Calendar getStartOfYear(final long timestamp, final TimeZone time_zone) { - final Calendar c = Calendar.getInstance(time_zone); - c.setTimeInMillis(timestamp); - c.set(Calendar.DAY_OF_YEAR, 1); - c.set(Calendar.HOUR_OF_DAY, 0); - c.set(Calendar.MINUTE, 0); - c.set(Calendar.SECOND, 0); - c.set(Calendar.MILLISECOND, 0); - return c; - } - - /** - * Returns a Calendar object corresponding the beginning of the month in which the specified - * timestamp occurs. This operation is performed based on the specified time zone. - * @param timestamp the epoch time - * @param time_zone the time zone used to determine the beginning of the month - * @return a Calendar object corresponding to the beginning of the month - */ - private static Calendar getStartOfMonth(final long timestamp, final TimeZone time_zone) { - final Calendar c = Calendar.getInstance(time_zone); - c.setTimeInMillis(timestamp); - c.set(Calendar.DAY_OF_MONTH, 1); - c.set(Calendar.HOUR_OF_DAY, 0); - c.set(Calendar.MINUTE, 0); - c.set(Calendar.SECOND, 0); - c.set(Calendar.MILLISECOND, 0); - return c; + public static Calendar previousInterval(final long ts, final int interval, + final int unit, final TimeZone tz) { + if (ts < 0) { + throw new IllegalArgumentException("Timestamp cannot be less than zero"); + } + if (interval < 1) { + throw new IllegalArgumentException("Interval must be greater than zero"); + } + + int unit_override = unit; + int interval_override = interval; + final Calendar calendar; + if (tz == null) { + calendar = Calendar.getInstance(timezones.get(UTC_ID)); + } else { + calendar = Calendar.getInstance(tz); + } + + switch (unit_override) { + case Calendar.MILLISECOND: + if (1000 % interval_override == 0) { + calendar.setTimeInMillis(ts); + calendar.set(Calendar.MILLISECOND, 0); + if (interval_override > 1000) { + calendar.add(Calendar.MILLISECOND, -interval_override); + } + } else { + // from top of minute + calendar.setTimeInMillis(ts); + calendar.set(Calendar.MILLISECOND, 0); + calendar.set(Calendar.SECOND, 0); + } + break; + case Calendar.SECOND: + if (60 % interval_override == 0) { + calendar.setTimeInMillis(ts); + calendar.set(Calendar.MILLISECOND, 0); + calendar.set(Calendar.SECOND, 0); + if (interval_override > 60) { + calendar.add(Calendar.SECOND, -interval_override); + } + } else { + // from top of hour + calendar.setTimeInMillis(ts); + calendar.set(Calendar.MILLISECOND, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MINUTE, 0); + } + break; + case Calendar.MINUTE: + if (60 % interval_override == 0) { + calendar.setTimeInMillis(ts); + calendar.set(Calendar.MILLISECOND, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MINUTE, 0); + if (interval_override > 60) { + calendar.add(Calendar.MINUTE, -interval_override); + } + } else { + // from top of day + calendar.setTimeInMillis(ts); + calendar.set(Calendar.MILLISECOND, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.HOUR_OF_DAY, 0); + } + break; + case Calendar.HOUR_OF_DAY: + if (24 % interval_override == 0) { + calendar.setTimeInMillis(ts); + calendar.set(Calendar.MILLISECOND, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.HOUR_OF_DAY, 0); + if (interval_override > 24) { + calendar.add(Calendar.HOUR_OF_DAY, -interval_override); + } + } else { + // from top of month + calendar.setTimeInMillis(ts); + calendar.set(Calendar.MILLISECOND, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.DAY_OF_MONTH, 1); + } + break; + case Calendar.DAY_OF_MONTH: + if (interval_override == 1) { + calendar.setTimeInMillis(ts); + calendar.set(Calendar.MILLISECOND, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.DAY_OF_MONTH, 1); + } else { + // from top of year + calendar.setTimeInMillis(ts); + calendar.set(Calendar.MILLISECOND, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.DAY_OF_MONTH, 1); + calendar.set(Calendar.MONTH, 0); + } + break; + case Calendar.DAY_OF_WEEK: + if (2 % interval_override == 0) { + calendar.setTimeInMillis(ts); + calendar.set(Calendar.MILLISECOND, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.DAY_OF_WEEK, calendar.getFirstDayOfWeek()); + } else { + // from top of year + calendar.setTimeInMillis(ts); + calendar.set(Calendar.MILLISECOND, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MONTH, 0); + calendar.set(Calendar.DAY_OF_WEEK, calendar.getFirstDayOfWeek()); + } + unit_override = Calendar.DAY_OF_MONTH; + interval_override = 7; + break; + case Calendar.WEEK_OF_YEAR: + // from top of year + calendar.setTimeInMillis(ts); + calendar.set(Calendar.MILLISECOND, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.DAY_OF_MONTH, 1); + calendar.set(Calendar.MONTH, 0); + break; + case Calendar.MONTH: + case Calendar.YEAR: + calendar.setTimeInMillis(ts); + calendar.set(Calendar.MILLISECOND, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.DAY_OF_MONTH, 1); + calendar.set(Calendar.MONTH, 0); + break; + default: + throw new IllegalArgumentException("Unexpected unit_overrides of type: " + + unit_override); + } + + if (calendar.getTimeInMillis() == ts) { + return calendar; + } + // TODO optimize a bit. We probably don't need to go past then back. + while (calendar.getTimeInMillis() <= ts) { + calendar.add(unit_override, interval_override); + } + calendar.add(unit_override, -interval_override); + return calendar; } /** - * Returns a Calendar object corresponding the beginning of the week in which the specified - * timestamp occurs. This operation is performed based on the specified time zone. - * @param timestamp the epoch time - * @param time_zone the time zone used to determine the beginning of the week - * @return a Calendar object corresponding to the beginning of the week + * Return the proper Calendar time unit as an integer given the string + * @param units The unit to parse + * @return An integer matching a Calendar. enum + * @throws IllegalArgumentException if the unit is null, empty or doesn't + * match one of the configured units. + * @since 2.3 */ - private static Calendar getStartOfWeek(final long timestamp, final TimeZone time_zone) { - final Calendar c = Calendar.getInstance(time_zone); - c.setTimeInMillis(timestamp); - c.set(Calendar.DAY_OF_WEEK, 1); // 1-sun, 2-mon. - c.set(Calendar.HOUR_OF_DAY, 0); - c.set(Calendar.MINUTE, 0); - c.set(Calendar.SECOND, 0); - c.set(Calendar.MILLISECOND, 0); - return c; + public static int unitsToCalendarType(final String units) { + if (units == null || units.isEmpty()) { + throw new IllegalArgumentException("Units cannot be null or empty"); + } + + final String lc = units.toLowerCase(); + if (lc.equals("ms")) { + return Calendar.MILLISECOND; + } else if (lc.equals("s")) { + return Calendar.SECOND; + } else if (lc.equals("m")) { + return Calendar.MINUTE; + } else if (lc.equals("h")) { + return Calendar.HOUR_OF_DAY; + } else if (lc.equals("d")) { + return Calendar.DAY_OF_MONTH; + } else if (lc.equals("w")) { + return Calendar.DAY_OF_WEEK; + } else if (lc.equals("n")) { + return Calendar.MONTH; + } else if (lc.equals("y")) { + return Calendar.YEAR; + } + throw new IllegalArgumentException("Unrecognized unit type: " + units); } - /** - * Returns a Calendar object corresponding the beginning of the day in which the specified - * timestamp occurs. This operation is performed based on the specified time zone. - * @param timestamp the epoch time - * @param time_zone the time zone used to determine the beginning of the day - * @return a Calendar object corresponding to the beginning of the day - */ - private static Calendar getStartOfDay(final long timestamp, final TimeZone time_zone) { - final Calendar c = Calendar.getInstance(time_zone); - c.setTimeInMillis(timestamp); - c.set(Calendar.HOUR_OF_DAY, 0); - c.set(Calendar.MINUTE, 0); - c.set(Calendar.SECOND, 0); - c.set(Calendar.MILLISECOND, 0); - return c; - } } diff --git a/test/core/SeekableViewsForTest.java b/test/core/SeekableViewsForTest.java index 4b638e5ac6..bc6463da1c 100644 --- a/test/core/SeekableViewsForTest.java +++ b/test/core/SeekableViewsForTest.java @@ -93,7 +93,7 @@ public static SeekableView generator(final long start_time, } /** Iterates an array of data points. */ - private static class MockSeekableView implements SeekableView { + public static class MockSeekableView implements SeekableView { private final DataPoint[] data_points; private int index = 0; @@ -128,6 +128,10 @@ public void seek(long timestamp) { } } } + + public void resetIndex() { + index = 0; + } } /** Generates a sequence of data points. */ diff --git a/test/core/TestDownsampler.java b/test/core/TestDownsampler.java index ccaa1eba5a..07f7296cdd 100644 --- a/test/core/TestDownsampler.java +++ b/test/core/TestDownsampler.java @@ -19,13 +19,13 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; -import java.util.Arrays; import java.util.Calendar; import java.util.List; import java.util.TimeZone; import com.google.common.collect.Lists; +import net.opentsdb.core.SeekableViewsForTest.MockSeekableView; import net.opentsdb.utils.DateTime; import org.junit.Before; @@ -55,8 +55,15 @@ public class TestDownsampler { (int)DateTime.parseDuration("10s"); private static final Aggregator AVG = Aggregators.get("avg"); private static final Aggregator SUM = Aggregators.get("sum"); - private static final TimeZone UTC_TIME_ZONE = DateTime.timezones.get("UTC"); private static final TimeZone EST_TIME_ZONE = DateTime.timezones.get("EST"); + //30 minute offset + final static TimeZone AF = DateTime.timezones.get("Asia/Kabul"); + // 12h offset w/o DST + final static TimeZone TV = DateTime.timezones.get("Pacific/Funafuti"); + // 12h offset w DST + final static TimeZone FJ = DateTime.timezones.get("Pacific/Fiji"); + // Tue, 15 Dec 2015 04:02:25.123 UTC + final static long DST_TS = 1450137600000L; private SeekableView source; private Downsampler downsampler; @@ -70,7 +77,7 @@ public void before() { @Test public void testDownsampler() { specification = new DownsamplingSpecification("1000s-avg"); - downsampler = new Downsampler(source, specification, 0, 0, TimeZone.getDefault(), false); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); verify(source, never()).next(); List values = Lists.newArrayList(); List timestamps_in_millis = Lists.newArrayList(); @@ -151,7 +158,7 @@ public void testDownsampler_10seconds() { MutableDataPoint.ofDoubleValue(BASE_TIME + 5000L * 10, 1024) })); specification = new DownsamplingSpecification("10s-sum"); - downsampler = new Downsampler(source, specification, 0, 0, TimeZone.getDefault(), false); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); verify(source, never()).next(); List values = Lists.newArrayList(); List timestamps_in_millis = Lists.newArrayList(); @@ -220,7 +227,7 @@ public void testDownsampler_15seconds() { MutableDataPoint.ofLongValue(BASE_TIME + 55000L, 32) })); specification = new DownsamplingSpecification("15s-sum"); - downsampler = new Downsampler(source, specification, 0, 0, TimeZone.getDefault(), false); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); verify(source, never()).next(); List values = Lists.newArrayList(); List timestamps_in_millis = Lists.newArrayList(); @@ -253,7 +260,7 @@ public void testDownsampler_allFullRange() { MutableDataPoint.ofLongValue(BASE_TIME + 55000L, 32) })); specification = new DownsamplingSpecification("0all-sum"); - downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE, TimeZone.getDefault(), false); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); verify(source, never()).next(); List values = Lists.newArrayList(); @@ -282,7 +289,7 @@ public void testDownsampler_allFilterOnQuery() { })); specification = new DownsamplingSpecification("0all-sum"); downsampler = new Downsampler(source, specification, - BASE_TIME + 15000L, BASE_TIME + 45000L, TimeZone.getDefault(), false); + BASE_TIME + 15000L, BASE_TIME + 45000L); verify(source, never()).next(); List values = Lists.newArrayList(); List timestamps_in_millis = Lists.newArrayList(); @@ -310,7 +317,7 @@ public void testDownsampler_allFilterOnQueryOutOfRangeEarly() { })); specification = new DownsamplingSpecification("0all-sum"); downsampler = new Downsampler(source, specification, - BASE_TIME + 65000L, BASE_TIME + 75000L, TimeZone.getDefault(), false); + BASE_TIME + 65000L, BASE_TIME + 75000L); verify(source, never()).next(); List values = Lists.newArrayList(); List timestamps_in_millis = Lists.newArrayList(); @@ -336,7 +343,7 @@ public void testDownsampler_allFilterOnQueryOutOfRangeLate() { })); specification = new DownsamplingSpecification("0all-sum"); downsampler = new Downsampler(source, specification, - BASE_TIME - 15000L, BASE_TIME - 5000L, TimeZone.getDefault(), false); + BASE_TIME - 15000L, BASE_TIME - 5000L); verify(source, never()).next(); List values = Lists.newArrayList(); List timestamps_in_millis = Lists.newArrayList(); @@ -360,9 +367,9 @@ public void testDownsampler_calendar() { MutableDataPoint.ofLongValue(BASE_TIME + 45000L, 16), MutableDataPoint.ofLongValue(BASE_TIME + 55000L, 32) })); - specification = new DownsamplingSpecification("1d-sum"); - //specification.setTimezone(DateTime.timezones.get("America/Denver")); - downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE, DateTime.timezones.get("America/Denver"), true); + specification = new DownsamplingSpecification("1dc-sum"); + specification.setTimezone(DateTime.timezones.get("America/Denver")); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); verify(source, never()).next(); List values = Lists.newArrayList(); List timestamps_in_millis = Lists.newArrayList(); @@ -378,11 +385,423 @@ public void testDownsampler_calendar() { assertEquals(1356937200000L, timestamps_in_millis.get(0).longValue()); } + @Test + public void testDownsampler_calendarHour() { + source = spy(SeekableViewsForTest.fromArray(new DataPoint[] { + MutableDataPoint.ofLongValue(BASE_TIME, 1), + MutableDataPoint.ofLongValue(BASE_TIME + 1800000, 2), + MutableDataPoint.ofLongValue(BASE_TIME + 3599000L, 3), + MutableDataPoint.ofLongValue(BASE_TIME + 3600000L, 4), + MutableDataPoint.ofLongValue(BASE_TIME + 5400000L, 5), + MutableDataPoint.ofLongValue(BASE_TIME + 7199000L, 6) + })); + specification = new DownsamplingSpecification("1hc-sum"); + specification.setTimezone(TV); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); + + long ts = BASE_TIME; + double value = 6; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + ts += 3600000; + value = 15; + } + + // hour offset by 30m + ((MockSeekableView)source).resetIndex(); + specification = new DownsamplingSpecification("1hc-sum"); + specification.setTimezone(AF); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); + + ts = 1356996600000L; + value = 1; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + ts += 3600000; + if (value == 1) { + value = 9; + } else { + value = 11; + } + } + + // multiple hours + ((MockSeekableView)source).resetIndex(); + specification = new DownsamplingSpecification("4hc-sum"); + specification.setTimezone(AF); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); + + ts = 1356996600000L; + value = 21; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + } + } + + @Test + public void testDownsampler_calendarDay() { + source = spy(SeekableViewsForTest.fromArray(new DataPoint[] { + MutableDataPoint.ofLongValue(DST_TS, 1), + MutableDataPoint.ofLongValue(DST_TS + 86399000, 2), + MutableDataPoint.ofLongValue(DST_TS + 126001000L, 3), // falls to the next in FJ + MutableDataPoint.ofLongValue(DST_TS + 172799000L, 4), + MutableDataPoint.ofLongValue(DST_TS + 172800000L, 5), + MutableDataPoint.ofLongValue(DST_TS + 242999000L, 6) // falls within 30m offset + })); + + // control + specification = new DownsamplingSpecification("1dc-sum"); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); + + long ts = DST_TS; + double value = 3; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + ts += 86400000; + if (value == 3) { + value = 7; + } else if (value == 7) { + value = 11; + } + } + + // 12 hour offset from UTC + ((MockSeekableView)source).resetIndex(); + specification = new DownsamplingSpecification("1dc-sum"); + specification.setTimezone(TV); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); + + ts = 1450094400000L; + value = 1; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + ts += 86400000; + if (value == 1) { + value = 5; + } else if (value == 5) { + value = 9; + } else { + value = 6; + } + } + + // 11 hour offset from UTC + ((MockSeekableView)source).resetIndex(); + specification = new DownsamplingSpecification("1dc-sum"); + specification.setTimezone(FJ); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); + + ts = 1450090800000L; + value = 1; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + ts += 86400000; + if (value == 1) { + value = 2; + } else if (value == 2) { + value = 12; + } else { + value = 6; + } + } + + // 30m offset + ((MockSeekableView)source).resetIndex(); + specification = new DownsamplingSpecification("1dc-sum"); + specification.setTimezone(AF); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); + + ts = 1450121400000L; + value = 1; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + ts += 86400000; + if (value == 1) { + value = 5; + } else if (value == 5) { + value = 15; + } + } + + // multiple days + ((MockSeekableView)source).resetIndex(); + specification = new DownsamplingSpecification("3dc-sum"); + specification.setTimezone(AF); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); + + ts = 1450121400000L; + value = 21; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + } + } + + @Test + public void testDownsampler_calendarWeek() { + source = SeekableViewsForTest.fromArray(new DataPoint[] { + MutableDataPoint.ofLongValue(DST_TS, 1), // a Tuesday in UTC land + MutableDataPoint.ofLongValue(DST_TS + (86400000L * 7), 2), + MutableDataPoint.ofLongValue(1451129400000L, 3), // falls to the next in FJ + MutableDataPoint.ofLongValue(DST_TS + (86400000L * 21), 4), + MutableDataPoint.ofLongValue(1452367799000L, 5) // falls within 30m offset + }); + // control + specification = new DownsamplingSpecification("1wc-sum"); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); + + long ts = 1449964800000L; + double value = 1; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + if (ts == 1450569600000L) { + ts = 1451779200000L; // skips a week + } else { + ts += 86400000L * 7; + } + if (value == 1) { + value = 5; + } else { + value = 9; + } + } + + // 12 hour offset from UTC + ((MockSeekableView)source).resetIndex(); + specification = new DownsamplingSpecification("1wc-sum"); + specification.setTimezone(TV); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); + + ts = 1449921600000L; + value = 1; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + if (ts == 1450526400000L) { + ts = 1451736000000L; // skip a week + } else { + ts += 86400000L * 7; + } + if (value == 1) { + value = 5; + } else if (value == 5) { + value = 4; + } else { + value = 5; + } + } + + // 11 hour offset from UTC + ((MockSeekableView)source).resetIndex(); + specification = new DownsamplingSpecification("1wc-sum"); + specification.setTimezone(FJ); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); + + ts = 1449918000000L; + value = 1; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + ts += 86400000L * 7; + value++; + } + + // 30m offset + ((MockSeekableView)source).resetIndex(); + specification = new DownsamplingSpecification("1wc-sum"); + specification.setTimezone(AF); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); + + ts = 1449948600000L; + value = 1; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + if (ts == 1449948600000L) { + ts = 1450553400000L; + } else { + ts = 1451763000000L; + } + if (value == 1) { + value = 5; + } else { + value = 9; + } + } + + // multiple weeks + ((MockSeekableView)source).resetIndex(); + specification = new DownsamplingSpecification("2wc-sum"); + specification.setTimezone(AF); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); + + ts = 1449948600000L; + value = 6; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + ts = 1451158200000L; + value = 9; + } + } + + @Test + public void testDownsampler_calendarMonth() { + final long dec_1st = 1448928000000L; + source = spy(SeekableViewsForTest.fromArray(new DataPoint[] { + MutableDataPoint.ofLongValue(dec_1st, 1), + MutableDataPoint.ofLongValue(1451559600000L, 2), // falls to the next in FJ + MutableDataPoint.ofLongValue(1451606400000L, 3), // jan 1st + MutableDataPoint.ofLongValue(1454284800000L, 4), // feb 1st + MutableDataPoint.ofLongValue(1456704000000L, 5), // feb 29th (leap year) + MutableDataPoint.ofLongValue(1456772400000L, 6) // falls within 30m offset AF + })); + + // control + specification = new DownsamplingSpecification("1nc-sum"); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); + + long ts = dec_1st; + double value = 3; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + if (ts == 1448928000000L) { + ts = 1451606400000L; + } else { + ts = 1454284800000L; + value = 15; + } + } + + // 12h offset + ((MockSeekableView)source).resetIndex(); + specification = new DownsamplingSpecification("1nc-sum"); + specification.setTimezone(TV); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); + + ts = 1448884800000L; + value = 3; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + if (ts == 1448884800000L) { + ts = 1451563200000L; + } else if (ts == 1451563200000L) { + value = 9; + ts = 1454241600000L; + } else { + ts = 1456747200000L; + value = 6; + } + } + + // 11h offset + ((MockSeekableView)source).resetIndex(); + specification = new DownsamplingSpecification("1nc-sum"); + specification.setTimezone(FJ); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); + + ts = 1448881200000L; + value = 1; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + if (ts == 1448881200000L) { + ts = 1451559600000L; + value = 5; + } else if (ts == 1451559600000L) { + ts = 1454241600000L; + value = 9; + } else { + ts = 1456747200000L; + value = 6; + } + } + + // 30m offset + ((MockSeekableView)source).resetIndex(); + specification = new DownsamplingSpecification("1nc-sum"); + specification.setTimezone(AF); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); + + ts = 1448911800000L; + value = 3; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + if (ts == 1448911800000L) { + ts = 1451590200000L; + } else { + ts = 1454268600000L; + value = 15; + } + } + + // multiple months + ((MockSeekableView)source).resetIndex(); + specification = new DownsamplingSpecification("3nc-sum"); + specification.setTimezone(TV); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); + + ts = 1443614400000L; + value = 3; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + ts = 1451563200000L; + value = 18; + } + } + @Test public void testDownsampler_noData() { source = spy(SeekableViewsForTest.fromArray(new DataPoint[] { })); specification = new DownsamplingSpecification("1d-sum"); - downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE, TimeZone.getDefault(), false); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); verify(source, never()).next(); assertFalse(downsampler.hasNext()); } @@ -390,179 +809,124 @@ public void testDownsampler_noData() { @Test public void testDownsampler_noDataCalendar() { source = spy(SeekableViewsForTest.fromArray(new DataPoint[] { })); - specification = new DownsamplingSpecification("1m-sum"); - downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE, - UTC_TIME_ZONE, true); + specification = new DownsamplingSpecification("1mc-sum"); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); verify(source, never()).next(); assertFalse(downsampler.hasNext()); } @Test public void testDownsampler_1day() { - final DataPoint [] data_points = new DataPoint[4]; - long timestamp = DateTime.toStartOfDay(BASE_TIME, UTC_TIME_ZONE); - for (int i = 0; i < data_points.length; i++) { - long value = 1 << i; - data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); - - i += 1; - long startOfNextInterval = DateTime.toEndOfDay(timestamp, UTC_TIME_ZONE) + 1; - timestamp = timestamp + (startOfNextInterval - timestamp) / 2; - value = 1 << i; - data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); - timestamp = startOfNextInterval; - } - - System.out.println(Arrays.toString(data_points)); - - source = spy(SeekableViewsForTest.fromArray(data_points)); + source = spy(SeekableViewsForTest.fromArray(new DataPoint[] { + MutableDataPoint.ofLongValue(BASE_TIME, 1), + MutableDataPoint.ofLongValue(BASE_TIME + 43200000L, 2), + MutableDataPoint.ofLongValue(BASE_TIME + 86400000L, 4), + MutableDataPoint.ofLongValue(BASE_TIME + 129600000L, 8) + })); - downsampler = new Downsampler(source, Downsampler.ONE_DAY_INTERVAL, SUM); + downsampler = new Downsampler(source, 86400000, SUM); verify(source, never()).next(); - List values = Lists.newArrayList(); - List timestamps_in_millis = Lists.newArrayList(); + long timestamp = BASE_TIME; + double value = 3; while (downsampler.hasNext()) { DataPoint dp = downsampler.next(); assertFalse(dp.isInteger()); - values.add(dp.doubleValue()); - timestamps_in_millis.add(dp.timestamp()); - } - - assertEquals(2, values.size()); - timestamp = DateTime.toStartOfDay(BASE_TIME, UTC_TIME_ZONE); - for (int i = 0, j = 0; i < values.size(); i++) { - assertEquals((1 << j++) + (1 << j++), values.get(i), 0.0000001); - assertEquals(timestamp, timestamps_in_millis.get(i).longValue()); - timestamp = DateTime.toEndOfDay(timestamp, UTC_TIME_ZONE) + 1; + assertEquals(timestamp, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.000001); + timestamp = 1357084800000L; + value = 12; } } @Test public void testDownsampler_1day_timezone() { - final DataPoint [] data_points = new DataPoint[4]; - long timestamp = DateTime.toStartOfDay(BASE_TIME, UTC_TIME_ZONE) - EST_TIME_ZONE.getOffset(BASE_TIME); - for (int i = 0; i < data_points.length; i++) { - long value = 1 << i; - data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); - - i += 1; - long startOfNextInterval = DateTime.toEndOfDay(timestamp, EST_TIME_ZONE) + 1; - timestamp = timestamp + (startOfNextInterval - timestamp) / 2; - value = 1 << i; - data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); - timestamp = startOfNextInterval; - } + source = spy(SeekableViewsForTest.fromArray(new DataPoint[] { + MutableDataPoint.ofLongValue(1357016400000L, 1), + MutableDataPoint.ofLongValue(1357059600000L, 2), + MutableDataPoint.ofLongValue(1357102800000L, 4), + MutableDataPoint.ofLongValue(1357146000000L, 8) + })); - source = spy(SeekableViewsForTest.fromArray(data_points)); - specification = new DownsamplingSpecification("1d-sum"); - downsampler = new Downsampler(source, specification, 0, 0, EST_TIME_ZONE, true); + specification = new DownsamplingSpecification("1dc-sum"); + specification.setTimezone(EST_TIME_ZONE); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); verify(source, never()).next(); - List values = Lists.newArrayList(); - List timestamps_in_millis = Lists.newArrayList(); + + long timestamp = 1357016400000L; + double value = 3; while (downsampler.hasNext()) { DataPoint dp = downsampler.next(); assertFalse(dp.isInteger()); - values.add(dp.doubleValue()); - timestamps_in_millis.add(dp.timestamp()); - } - - assertEquals(2, values.size()); - timestamp = DateTime.toStartOfDay(BASE_TIME, UTC_TIME_ZONE) - EST_TIME_ZONE.getOffset(BASE_TIME); - for (int i = 0, j = 0; i < values.size(); i++) { - assertEquals((1 << j++) + (1 << j++), values.get(i), 0.0000001); - assertEquals(timestamp, timestamps_in_millis.get(i).longValue()); - timestamp = DateTime.toEndOfDay(timestamp, EST_TIME_ZONE) + 1; + assertEquals(timestamp, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.000001); + timestamp = 1357102800000L; + value = 12; } } @Test public void testDownsampler_1week() { - final DataPoint [] data_points = new DataPoint[4]; - long timestamp = DateTime.toStartOfWeek(BASE_TIME, UTC_TIME_ZONE); - for (int i = 0; i < data_points.length; i++) { - long value = 1 << i; - data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); - - i += 1; - long startOfNextInterval = DateTime.toEndOfWeek(timestamp, UTC_TIME_ZONE) + 1; - timestamp = timestamp + (startOfNextInterval - timestamp) / 2; - value = 1 << i; - data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); - timestamp = startOfNextInterval; - } - - source = spy(SeekableViewsForTest.fromArray(data_points)); + source = spy(SeekableViewsForTest.fromArray(new DataPoint[] { + MutableDataPoint.ofLongValue(1356825600000L, 1), + MutableDataPoint.ofLongValue(1357128000000L, 2), + MutableDataPoint.ofLongValue(1357430400000L, 4), + MutableDataPoint.ofLongValue(1357732800000L, 8) + })); - specification = new DownsamplingSpecification("1w-sum"); - downsampler = new Downsampler(source, specification, 0, 0, UTC_TIME_ZONE, true); + specification = new DownsamplingSpecification("1wc-sum"); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); verify(source, never()).next(); - List values = Lists.newArrayList(); - List timestamps_in_millis = Lists.newArrayList(); + long timestamp = 1356825600000L; + double value = 3; while (downsampler.hasNext()) { DataPoint dp = downsampler.next(); assertFalse(dp.isInteger()); - values.add(dp.doubleValue()); - timestamps_in_millis.add(dp.timestamp()); - } - - assertEquals(2, values.size()); - timestamp = DateTime.toStartOfWeek(BASE_TIME, UTC_TIME_ZONE); - for (int i = 0, j = 0; i < values.size(); i++) { - assertEquals((1 << j++) + (1 << j++), values.get(i), 0.0000001); - assertEquals(timestamp, timestamps_in_millis.get(i).longValue()); - timestamp = DateTime.toEndOfWeek(timestamp, UTC_TIME_ZONE) + 1; + assertEquals(timestamp, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.000001); + timestamp = 1357430400000L; + value = 12; } } @Test public void testDownsampler_1week_timezone() { - final DataPoint [] data_points = new DataPoint[4]; - long timestamp = DateTime.toStartOfWeek(BASE_TIME, UTC_TIME_ZONE) - EST_TIME_ZONE.getOffset(BASE_TIME); - for (int i = 0; i < data_points.length; i++) { - long value = 1 << i; - data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); - - i += 1; - long startOfNextInterval = DateTime.toEndOfWeek(timestamp, EST_TIME_ZONE) + 1; - timestamp = timestamp + (startOfNextInterval - timestamp) / 2; - value = 1 << i; - data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); - timestamp = startOfNextInterval; - } - - source = spy(SeekableViewsForTest.fromArray(data_points)); + source = spy(SeekableViewsForTest.fromArray(new DataPoint[] { + MutableDataPoint.ofLongValue(1356843600000L, 1), + MutableDataPoint.ofLongValue(1357146000000L, 2), + MutableDataPoint.ofLongValue(1357448400000L, 4), + MutableDataPoint.ofLongValue(1357750800000L, 8) + })); - specification = new DownsamplingSpecification("1w-sum"); - downsampler = new Downsampler(source, specification, 0, 0, EST_TIME_ZONE, true); + specification = new DownsamplingSpecification("1wc-sum"); + specification.setTimezone(EST_TIME_ZONE); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); verify(source, never()).next(); - List values = Lists.newArrayList(); - List timestamps_in_millis = Lists.newArrayList(); + long timestamp = 1356843600000L; + double value = 3; while (downsampler.hasNext()) { DataPoint dp = downsampler.next(); assertFalse(dp.isInteger()); - values.add(dp.doubleValue()); - timestamps_in_millis.add(dp.timestamp()); - } - - assertEquals(2, values.size()); - timestamp = DateTime.toStartOfWeek(BASE_TIME, UTC_TIME_ZONE) - EST_TIME_ZONE.getOffset(BASE_TIME); - for (int i = 0, j = 0; i < values.size(); i++) { - assertEquals((1 << j++) + (1 << j++), values.get(i), 0.0000001); - assertEquals(timestamp, timestamps_in_millis.get(i).longValue()); - timestamp = DateTime.toEndOfWeek(timestamp, EST_TIME_ZONE) + 1; + assertEquals(timestamp, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.000001); + timestamp = 1357448400000L; + value = 12; } } @Test public void testDownsampler_1month() { + final int field = DateTime.unitsToCalendarType("n"); final DataPoint [] data_points = new DataPoint[24]; - long timestamp = DateTime.toStartOfMonth(BASE_TIME, UTC_TIME_ZONE); + Calendar c = DateTime.previousInterval(BASE_TIME, 1, field); + //long timestamp = DateTime.toStartOfMonth(BASE_TIME, UTC_TIME_ZONE); + long timestamp = c.getTimeInMillis(); for (int i = 0; i < data_points.length; i++) { long value = 1 << i; data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); i += 1; - long startOfNextInterval = DateTime.toEndOfMonth(timestamp, UTC_TIME_ZONE) + 1; + c.add(field, 1); + long startOfNextInterval = c.getTimeInMillis() + 1; timestamp = timestamp + (startOfNextInterval - timestamp) / 2; value = 1 << i; data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); @@ -570,25 +934,18 @@ public void testDownsampler_1month() { } source = spy(SeekableViewsForTest.fromArray(data_points)); - - specification = new DownsamplingSpecification("1n-sum"); - downsampler = new Downsampler(source, specification, 0, 0, UTC_TIME_ZONE, true); + + specification = new DownsamplingSpecification("1nc-sum"); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); verify(source, never()).next(); - List values = Lists.newArrayList(); - List timestamps_in_millis = Lists.newArrayList(); + c = DateTime.previousInterval(BASE_TIME, 1, field); + int j = 0; while (downsampler.hasNext()) { DataPoint dp = downsampler.next(); assertFalse(dp.isInteger()); - values.add(dp.doubleValue()); - timestamps_in_millis.add(dp.timestamp()); - } - - assertEquals(12, values.size()); - timestamp = DateTime.toStartOfMonth(BASE_TIME, UTC_TIME_ZONE); - for (int i = 0, j = 0; i < values.size(); i++) { - assertEquals((1 << j++) + (1 << j++), values.get(i), 0.0000001); - assertEquals(timestamp, timestamps_in_millis.get(i).longValue()); - timestamp = DateTime.toEndOfMonth(timestamp, UTC_TIME_ZONE) + 1; + assertEquals((1 << j++) + (1 << j++), dp.doubleValue(), 0.0000001); + assertEquals(c.getTimeInMillis(), dp.timestamp()); + c.add(field, 1); } } @@ -627,37 +984,35 @@ public void testDownsampler_1month_alt() { source = spy(SeekableViewsForTest.fromArray(data_points)); - specification = new DownsamplingSpecification("1d-sum"); - downsampler = new Downsampler(source, specification, 0, 0, UTC_TIME_ZONE, true); + specification = new DownsamplingSpecification("1dc-sum"); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); verify(source, never()).next(); - List values = Lists.newArrayList(); - List timestamps_in_millis = Lists.newArrayList(); + final int field = DateTime.unitsToCalendarType("n"); + final Calendar c = DateTime.previousInterval(1380585600000L, 1, field); + long timestamp = c.getTimeInMillis(); while (downsampler.hasNext()) { DataPoint dp = downsampler.next(); assertFalse(dp.isInteger()); - values.add(dp.doubleValue()); - timestamps_in_millis.add(dp.timestamp()); - } - - assertEquals(12, values.size()); - long timestamp = DateTime.toStartOfMonth(data_points[0].timestamp(), UTC_TIME_ZONE); - for (int i = 0; i < values.size(); i++) { - assertEquals(1, values.get(i), 0.0000001); - assertEquals(timestamp, timestamps_in_millis.get(i).longValue()); - timestamp = DateTime.toEndOfMonth(timestamp, UTC_TIME_ZONE) + 1; + assertEquals(1, dp.doubleValue(), 0.0000001); + assertEquals(timestamp, dp.timestamp()); + c.add(field, 1); + timestamp = c.getTimeInMillis(); } } @Test public void testDownsampler_2months() { + final int field = DateTime.unitsToCalendarType("n"); final DataPoint [] data_points = new DataPoint[24]; - long timestamp = DateTime.toStartOfMonth(BASE_TIME, UTC_TIME_ZONE); + Calendar c = DateTime.previousInterval(BASE_TIME, 1, field); + long timestamp = c.getTimeInMillis(); for (int i = 0; i < data_points.length; i++) { long value = 1 << i; data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); i += 1; - long startOfNextInterval = DateTime.toEndOfMonth(timestamp, UTC_TIME_ZONE) + 1; + c.add(field, 1); + long startOfNextInterval = c.getTimeInMillis(); timestamp = timestamp + (startOfNextInterval - timestamp) / 2; value = 1 << i; data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); @@ -666,42 +1021,37 @@ public void testDownsampler_2months() { source = spy(SeekableViewsForTest.fromArray(data_points)); - specification = new DownsamplingSpecification("2n-sum"); - downsampler = new Downsampler(source, specification, 0, 0, UTC_TIME_ZONE, true); + specification = new DownsamplingSpecification("2nc-sum"); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); verify(source, never()).next(); - List values = Lists.newArrayList(); - List timestamps_in_millis = Lists.newArrayList(); + int j = 0; + c = DateTime.previousInterval(BASE_TIME, 1, field); while (downsampler.hasNext()) { DataPoint dp = downsampler.next(); assertFalse(dp.isInteger()); - values.add(dp.doubleValue()); - timestamps_in_millis.add(dp.timestamp()); - } - - assertEquals(6, values.size()); - timestamp = DateTime.toStartOfMonth(BASE_TIME, UTC_TIME_ZONE); - for (int i = 0, j = 0; i < values.size(); i++) { long value = 0; for (int k = 0; k < 4; k++) { value += (1 << j++); } - assertEquals(value, values.get(i), 0.0000001); - assertEquals(timestamp, timestamps_in_millis.get(i).longValue()); - timestamp = DateTime.toEndOfMonth(timestamp, UTC_TIME_ZONE) + 1; - timestamp = DateTime.toEndOfMonth(timestamp, UTC_TIME_ZONE) + 1; + assertEquals(value, dp.doubleValue(), 0.0000001); + assertEquals(c.getTimeInMillis(), dp.timestamp()); + c.add(field, 2); } } - + @Test public void testDownsampler_1month_timezone() { + final int field = DateTime.unitsToCalendarType("n"); final DataPoint [] data_points = new DataPoint[24]; - long timestamp = DateTime.toStartOfMonth(BASE_TIME, UTC_TIME_ZONE) - EST_TIME_ZONE.getOffset(BASE_TIME); + Calendar c = DateTime.previousInterval(1357016400000L, 1, field, EST_TIME_ZONE); + long timestamp = c.getTimeInMillis(); for (int i = 0; i < data_points.length; i++) { long value = 1 << i; data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); i += 1; - long startOfNextInterval = DateTime.toEndOfMonth(timestamp, EST_TIME_ZONE) + 1; + c.add(field, 1); + long startOfNextInterval = c.getTimeInMillis(); timestamp = timestamp + (startOfNextInterval - timestamp) / 2; value = 1 << i; data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); @@ -710,37 +1060,34 @@ public void testDownsampler_1month_timezone() { source = spy(SeekableViewsForTest.fromArray(data_points)); - specification = new DownsamplingSpecification("1n-sum"); - downsampler = new Downsampler(source, specification, 0, 0, EST_TIME_ZONE, true); + specification = new DownsamplingSpecification("1nc-sum"); + specification.setTimezone(EST_TIME_ZONE); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); verify(source, never()).next(); - List values = Lists.newArrayList(); - List timestamps_in_millis = Lists.newArrayList(); + int j = 0; + c = DateTime.previousInterval(1357016400000L, 1, field, EST_TIME_ZONE); while (downsampler.hasNext()) { DataPoint dp = downsampler.next(); assertFalse(dp.isInteger()); - values.add(dp.doubleValue()); - timestamps_in_millis.add(dp.timestamp()); - } - - assertEquals(12, values.size()); - timestamp = DateTime.toStartOfMonth(BASE_TIME, UTC_TIME_ZONE) - EST_TIME_ZONE.getOffset(BASE_TIME); - for (int i = 0, j = 0; i < values.size(); i++) { - assertEquals((1 << j++) + (1 << j++), values.get(i), 0.0000001); - assertEquals(timestamp, timestamps_in_millis.get(i).longValue()); - timestamp = DateTime.toEndOfMonth(timestamp, EST_TIME_ZONE) + 1; + assertEquals((1 << j++) + (1 << j++), dp.doubleValue(), 0.0000001); + assertEquals(c.getTimeInMillis(), dp.timestamp()); + c.add(field, 1); } } @Test public void testDownsampler_1year() { + final int field = DateTime.unitsToCalendarType("y"); final DataPoint [] data_points = new DataPoint[4]; - long timestamp = DateTime.toStartOfYear(BASE_TIME, UTC_TIME_ZONE); + Calendar c = DateTime.previousInterval(BASE_TIME, 1, field); + long timestamp = c.getTimeInMillis(); for (int i = 0; i < data_points.length; i++) { long value = 1 << i; data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); i += 1; - long startOfNextInterval = DateTime.toEndOfYear(timestamp, UTC_TIME_ZONE) + 1; + c.add(field, 1); + long startOfNextInterval = c.getTimeInMillis(); timestamp = timestamp + (startOfNextInterval - timestamp) / 2; value = 1 << i; data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); @@ -749,37 +1096,34 @@ public void testDownsampler_1year() { source = spy(SeekableViewsForTest.fromArray(data_points)); - specification = new DownsamplingSpecification("1y-sum"); - downsampler = new Downsampler(source, specification, 0, 0, UTC_TIME_ZONE, true); + specification = new DownsamplingSpecification("1yc-sum"); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); verify(source, never()).next(); - List values = Lists.newArrayList(); - List timestamps_in_millis = Lists.newArrayList(); + int j = 0; + c = DateTime.previousInterval(BASE_TIME, 1, field); while (downsampler.hasNext()) { DataPoint dp = downsampler.next(); assertFalse(dp.isInteger()); - values.add(dp.doubleValue()); - timestamps_in_millis.add(dp.timestamp()); - } - - assertEquals(2, values.size()); - timestamp = DateTime.toStartOfYear(BASE_TIME, UTC_TIME_ZONE); - for (int i = 0, j = 0; i < values.size(); i++) { - assertEquals((1 << j++) + (1 << j++), values.get(i), 0.0000001); - assertEquals(timestamp, timestamps_in_millis.get(i).longValue()); - timestamp = DateTime.toEndOfYear(timestamp, UTC_TIME_ZONE) + 1; + assertEquals((1 << j++) + (1 << j++), dp.doubleValue(), 0.0000001); + assertEquals(c.getTimeInMillis(), dp.timestamp()); + c.add(field, 1); } } @Test public void testDownsampler_1year_timezone() { + final int field = DateTime.unitsToCalendarType("y"); final DataPoint [] data_points = new DataPoint[4]; - long timestamp = DateTime.toStartOfYear(BASE_TIME, UTC_TIME_ZONE) - EST_TIME_ZONE.getOffset(BASE_TIME); + Calendar c = DateTime.previousInterval(1357016400000L, 1, field, + EST_TIME_ZONE); + long timestamp = c.getTimeInMillis(); for (int i = 0; i < data_points.length; i++) { long value = 1 << i; data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); i += 1; - long startOfNextInterval = DateTime.toEndOfYear(timestamp, EST_TIME_ZONE) + 1; + c.add(field, 1); + long startOfNextInterval = c.getTimeInMillis(); timestamp = timestamp + (startOfNextInterval - timestamp) / 2; value = 1 << i; data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); @@ -788,24 +1132,18 @@ public void testDownsampler_1year_timezone() { source = spy(SeekableViewsForTest.fromArray(data_points)); - specification = new DownsamplingSpecification("1y-sum"); - downsampler = new Downsampler(source, specification, 0, 0, EST_TIME_ZONE, true); + specification = new DownsamplingSpecification("1yc-sum"); + specification.setTimezone(EST_TIME_ZONE); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); verify(source, never()).next(); - List values = Lists.newArrayList(); - List timestamps_in_millis = Lists.newArrayList(); + int j = 0; + c = DateTime.previousInterval(1357016400000L, 1, field, EST_TIME_ZONE); while (downsampler.hasNext()) { DataPoint dp = downsampler.next(); assertFalse(dp.isInteger()); - values.add(dp.doubleValue()); - timestamps_in_millis.add(dp.timestamp()); - } - - assertEquals(2, values.size()); - timestamp = DateTime.toStartOfYear(BASE_TIME, UTC_TIME_ZONE) - EST_TIME_ZONE.getOffset(BASE_TIME); - for (int i = 0, j = 0; i < values.size(); i++) { - assertEquals((1 << j++) + (1 << j++), values.get(i), 0.0000001); - assertEquals(timestamp, timestamps_in_millis.get(i).longValue()); - timestamp = DateTime.toEndOfYear(timestamp, EST_TIME_ZONE) + 1; + assertEquals((1 << j++) + (1 << j++), dp.doubleValue(), 0.0000001); + assertEquals(c.getTimeInMillis(), dp.timestamp()); + c.add(field, 1); } } @@ -839,66 +1177,42 @@ public void testSeek() { @Test public void testSeek_useCalendar() { - final DataPoint [] data_points = new DataPoint[4]; - long timestamp = DateTime.toStartOfYear(BASE_TIME, UTC_TIME_ZONE); - final Calendar c = Calendar.getInstance(UTC_TIME_ZONE); - c.setTimeInMillis(timestamp); - for (int i = 0; i < data_points.length; i++) { - long value = 1 << i; - data_points[i] = MutableDataPoint.ofLongValue(timestamp, value); - timestamp = DateTime.toEndOfYear(timestamp, UTC_TIME_ZONE) + 1; - } - - source = spy(SeekableViewsForTest.fromArray(data_points)); + source = spy(SeekableViewsForTest.fromArray(new DataPoint[] { + MutableDataPoint.ofLongValue(1356998400000L, 1), + MutableDataPoint.ofLongValue(1388534400000L, 2), + MutableDataPoint.ofLongValue(1420070400000L, 4), + MutableDataPoint.ofLongValue(1451606400000L, 8) + })); - c.add(Calendar.YEAR, 2); specification = new DownsamplingSpecification("1y-sum"); - downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE, - UTC_TIME_ZONE, true); - System.out.println("SEEK: " + c.getTimeInMillis()); - downsampler.seek(c.getTimeInMillis()); + specification.setUseCalendar(true); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); + + downsampler.seek(1420070400000L); verify(source, never()).next(); - List values = Lists.newArrayList(); - List timestamps_in_millis = Lists.newArrayList(); + + long timestamp = 1420070400000L; + double value = 4; while (downsampler.hasNext()) { DataPoint dp = downsampler.next(); assertFalse(dp.isInteger()); - values.add(dp.doubleValue()); - timestamps_in_millis.add(dp.timestamp()); + assertEquals(timestamp, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.0000001); + timestamp = 1451606400000L; + value = 8; } - assertEquals(2, values.size()); - timestamp = DateTime.toStartOfYear(c.getTimeInMillis(), UTC_TIME_ZONE); - for (int i = 2; i < values.size(); i++) { - assertEquals(1 << i, values.get(i), 0.0000001); - assertEquals(timestamp, timestamps_in_millis.get(i).longValue()); - timestamp = DateTime.toEndOfYear(timestamp, UTC_TIME_ZONE) + 1; - } + ((MockSeekableView)source).resetIndex(); + specification = new DownsamplingSpecification("1yc-sum"); + downsampler = new Downsampler(source, specification, 0, Long.MAX_VALUE); + downsampler.seek(1420070400001L); - source = spy(SeekableViewsForTest.fromArray(data_points)); - - c.add(Calendar.MILLISECOND, 1); - specification = new DownsamplingSpecification("1y-sum"); - downsampler = new Downsampler(source, specification, 0, 0, UTC_TIME_ZONE, true); - downsampler.seek(c.getTimeInMillis()); - verify(source, never()).next(); - values = Lists.newArrayList(); - timestamps_in_millis = Lists.newArrayList(); while (downsampler.hasNext()) { DataPoint dp = downsampler.next(); assertFalse(dp.isInteger()); - values.add(dp.doubleValue()); - timestamps_in_millis.add(dp.timestamp()); + assertEquals(timestamp, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.0000001); } - - assertEquals(1, values.size()); - timestamp = DateTime.toStartOfYear(c.getTimeInMillis(), UTC_TIME_ZONE); - for (int i = 3; i < values.size(); i++) { - assertEquals(1 << i, values.get(i), 0.0000001); - assertEquals(timestamp, timestamps_in_millis.get(i).longValue()); - timestamp = DateTime.toEndOfYear(timestamp, UTC_TIME_ZONE) + 1; - } - } @Test @@ -995,7 +1309,6 @@ public void testSeek_abandoningIncompleteInterval() { public void testToString() { downsampler = new Downsampler(source, THOUSAND_SEC_INTERVAL, AVG); DataPoint dp = downsampler.next(); - System.out.println(downsampler.toString()); assertTrue(downsampler.toString().contains(dp.toString())); } } diff --git a/test/core/TestDownsamplingSpecification.java b/test/core/TestDownsamplingSpecification.java index 0fc51f0368..9e541f55ec 100644 --- a/test/core/TestDownsamplingSpecification.java +++ b/test/core/TestDownsamplingSpecification.java @@ -14,9 +14,20 @@ import org.junit.Test; +import net.opentsdb.utils.DateTime; + import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.TimeZone; public class TestDownsamplingSpecification { + final long interval = 60000L; + final FillPolicy fill_policy = FillPolicy.ZERO; + final Aggregator function = Aggregators.SUM; + final TimeZone timezone = DateTime.timezones.get(DateTime.UTC_ID); + @Test public void testCtor() { final long interval = 1234567L; @@ -41,16 +52,6 @@ public void testStringCtor() { assertEquals(FillPolicy.NOT_A_NUMBER, ds.getFillPolicy()); } - @Test - public void testToString() { - assertEquals("DownsamplingSpecification{interval=4532019, function=zimsum, " - + "fillPolicy=NOT_A_NUMBER, stringInterval=null}", - new DownsamplingSpecification( - 4532019L, - Aggregators.ZIMSUM, - FillPolicy.NOT_A_NUMBER).toString()); - } - @Test(expected = RuntimeException.class) public void testBadInterval() { new DownsamplingSpecification("blah-avg-lerp"); @@ -70,5 +71,114 @@ public void testBadFillPolicy() { public void testNoneAgg() { new DownsamplingSpecification("1m-none-lerp"); } + + @Test (expected = IllegalArgumentException.class) + public void testCtorNegativeInterval() { + new DownsamplingSpecification(-1, function, fill_policy); + } + + @Test (expected = IllegalArgumentException.class) + public void testCtorZeroInterval() { + new DownsamplingSpecification(DownsamplingSpecification.NO_INTERVAL, + function, fill_policy); + } + + @Test (expected = IllegalArgumentException.class) + public void testCtorNullFunction() { + new DownsamplingSpecification(interval, null, fill_policy); + } + + @Test(expected = IllegalArgumentException.class) + public void testStringCtorNull() { + new DownsamplingSpecification(null); + } + + @Test(expected = IllegalArgumentException.class) + public void testStringCtorEmpty() { + new DownsamplingSpecification(""); + } + + @Test(expected = IllegalArgumentException.class) + public void testStringCtorNoIntervalString() { + new DownsamplingSpecification("blah-avg-lerp"); + } + + @Test(expected = IllegalArgumentException.class) + public void testStringCtorZeroInterval() { + new DownsamplingSpecification("0m-avg-lerp"); + } + + @Test(expected = IllegalArgumentException.class) + public void testStringCtorNegativeInterval() { + new DownsamplingSpecification("-60m-avg-lerp"); + } + + @Test(expected = IllegalArgumentException.class) + public void testStringCtorUnknownUnits() { + new DownsamplingSpecification("1j-avg-lerp"); + } + + @Test(expected = IllegalArgumentException.class) + public void testStringCtorMissingUnits() { + new DownsamplingSpecification("1-avg-lerp"); + } + + @Test(expected = IllegalArgumentException.class) + public void testStringCtorBadFunction() { + new DownsamplingSpecification("1m-hurp-lerp"); + } + + @Test(expected = IllegalArgumentException.class) + public void testStringCtorMissingFunction() { + new DownsamplingSpecification("1m"); + } + + @Test(expected = IllegalArgumentException.class) + public void testStringCtorBadFillPolicy() { + new DownsamplingSpecification("10m-avg-max"); + } + + @Test + public void testSetCalendar() { + DownsamplingSpecification ds = new DownsamplingSpecification("15m-avg"); + assertFalse(ds.useCalendar()); + + ds.setUseCalendar(true); + assertTrue(ds.useCalendar()); + + ds.setUseCalendar(false); + assertFalse(ds.useCalendar()); + } + + @Test + public void setTimezone() { + DownsamplingSpecification ds = new DownsamplingSpecification("15m-avg"); + assertEquals(timezone, ds.getTimezone()); + + final TimeZone tz = DateTime.timezones.get("America/Denver"); + ds.setTimezone(tz); + assertEquals(tz, ds.getTimezone()); + } + + @Test (expected = IllegalArgumentException.class) + public void setTimezoneNull() { + DownsamplingSpecification ds = new DownsamplingSpecification("15m-avg"); + ds.setTimezone(null); + } + + @Test + public void testToString() { + final String string = new DownsamplingSpecification( + 4532019L, + Aggregators.ZIMSUM, + FillPolicy.NOT_A_NUMBER).toString(); + + assertTrue(string.contains("interval=4532019")); + assertTrue(string.contains("function=zimsum")); + assertTrue(string.contains("fillPolicy=NOT_A_NUMBER")); + assertTrue(string.contains("useCalendar=false")); + assertTrue(string.contains("timeZone=UTC")); + } + } diff --git a/test/core/TestFillingDownsampler.java b/test/core/TestFillingDownsampler.java index b33dc68fd9..ef42ebc963 100644 --- a/test/core/TestFillingDownsampler.java +++ b/test/core/TestFillingDownsampler.java @@ -14,16 +14,30 @@ import org.junit.Test; +import net.opentsdb.core.SeekableViewsForTest.MockSeekableView; +import net.opentsdb.utils.DateTime; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.spy; import java.util.TimeZone; /** Tests {@link FillingDownsampler}. */ public class TestFillingDownsampler { private static final long BASE_TIME = 1356998400000L; - + //30 minute offset + final static TimeZone AF = DateTime.timezones.get("Asia/Kabul"); + // 12h offset w/o DST + final static TimeZone TV = DateTime.timezones.get("Pacific/Funafuti"); + // 12h offset w DST + final static TimeZone FJ = DateTime.timezones.get("Pacific/Fiji"); + // Tue, 15 Dec 2015 04:02:25.123 UTC + final static long DST_TS = 1450137600000L; + + private SeekableView source; + private Downsampler downsampler; private DownsamplingSpecification specification; /** Data with gaps: before, during, and after. */ @@ -45,7 +59,7 @@ public void testNaNMissingInterval() { specification = new DownsamplingSpecification("100ms-sum-nan"); final Downsampler downsampler = new FillingDownsampler(source, baseTime, - baseTime + 36 * 25L, specification, 0, 0, TimeZone.getDefault(), false); + baseTime + 36 * 25L, specification, 0, 0); long timestamp = baseTime; step(downsampler, timestamp, Double.NaN); @@ -78,7 +92,7 @@ public void testZeroMissingInterval() { specification = new DownsamplingSpecification("100ms-sum-zero"); final Downsampler downsampler = new FillingDownsampler(source, baseTime, - baseTime + 36 * 25L, specification, 0, 0, TimeZone.getDefault(), false); + baseTime + 36 * 25L, specification, 0, 0); long timestamp = baseTime; step(downsampler, timestamp, 0.); @@ -115,7 +129,7 @@ public void testWithoutMissingIntervals() { specification = new DownsamplingSpecification("100ms-sum-nan"); final Downsampler downsampler = new FillingDownsampler(source, baseTime, - baseTime + 12L * 25L, specification, 0, 0, TimeZone.getDefault(), false); + baseTime + 12L * 25L, specification, 0, 0); long timestamp = baseTime; step(downsampler, timestamp, 42.); @@ -148,7 +162,7 @@ public void testWithOutOfBoundsData() { specification = new DownsamplingSpecification("1m-sum-nan"); final Downsampler downsampler = new FillingDownsampler(source, baseTime, - baseTime + 60000L * 2L, specification, 0, 0, TimeZone.getDefault(), false); + baseTime + 60000L * 2L, specification, 0, 0); long timestamp = 1425335880000L; step(downsampler, timestamp, 30.); @@ -167,7 +181,7 @@ public void testWithOutOfBoundsDataEarly() { specification = new DownsamplingSpecification("1m-sum-nan"); final Downsampler downsampler = new FillingDownsampler(source, baseTime, - baseTime + 60000L * 2L, specification, 0, 0, TimeZone.getDefault(), false); + baseTime + 60000L * 2L, specification, 0, 0); long timestamp = 1425335880000L; step(downsampler, timestamp, Double.NaN); @@ -186,7 +200,7 @@ public void testWithOutOfBoundsDataLate() { specification = new DownsamplingSpecification("1m-sum-nan"); final Downsampler downsampler = new FillingDownsampler(source, baseTime, - baseTime + 60000L * 2L, specification, 0, 0, TimeZone.getDefault(), false); + baseTime + 60000L * 2L, specification, 0, 0); long timestamp = 1425335880000L; step(downsampler, timestamp, Double.NaN); @@ -208,7 +222,7 @@ public void testDownsampler_allFullRange() { specification = new DownsamplingSpecification("0all-sum-nan"); final Downsampler downsampler = new FillingDownsampler(source, BASE_TIME + 5000L,BASE_TIME + 55000L, specification, 0, - Long.MAX_VALUE, TimeZone.getDefault(), false); + Long.MAX_VALUE); step(downsampler, 0, 63); assertFalse(downsampler.hasNext()); @@ -228,7 +242,7 @@ public void testDownsampler_allFilterOnQuery() { specification = new DownsamplingSpecification("0all-sum-nan"); final Downsampler downsampler = new FillingDownsampler(source, BASE_TIME + 5000L,BASE_TIME + 55000L, specification, - BASE_TIME + 15000L, BASE_TIME + 45000L, TimeZone.getDefault(), false); + BASE_TIME + 15000L, BASE_TIME + 45000L); step(downsampler, BASE_TIME + 15000L, 14); assertFalse(downsampler.hasNext()); @@ -248,7 +262,7 @@ public void testDownsampler_allFilterOnQueryOutOfRangeEarly() { specification = new DownsamplingSpecification("0all-sum-nan"); final Downsampler downsampler = new FillingDownsampler(source, BASE_TIME + 5000L,BASE_TIME + 55000L, specification, - BASE_TIME + 65000L, BASE_TIME + 75000L, TimeZone.getDefault(), false); + BASE_TIME + 65000L, BASE_TIME + 75000L); assertFalse(downsampler.hasNext()); } @@ -267,8 +281,536 @@ public void testDownsampler_allFilterOnQueryOutOfRangeLate() { specification = new DownsamplingSpecification("0all-sum-nan"); final Downsampler downsampler = new FillingDownsampler(source, BASE_TIME + 5000L,BASE_TIME + 55000L, specification, - BASE_TIME - 15000L, BASE_TIME - 5000L, TimeZone.getDefault(), false); + BASE_TIME - 15000L, BASE_TIME - 5000L); + + assertFalse(downsampler.hasNext()); + } + + @Test + public void testDownsampler_calendarHour() { + source = SeekableViewsForTest.fromArray(new DataPoint[] { + MutableDataPoint.ofLongValue(BASE_TIME, 1), + MutableDataPoint.ofLongValue(BASE_TIME + 1800000, 2), + MutableDataPoint.ofLongValue(BASE_TIME + 3599000L, 3), + MutableDataPoint.ofLongValue(BASE_TIME + 3600000L, 4), + MutableDataPoint.ofLongValue(BASE_TIME + 5400000L, 5), + MutableDataPoint.ofLongValue(BASE_TIME + 7199000L, 6) + }); + specification = new DownsamplingSpecification("1hc-sum-nan"); + specification.setTimezone(TV); + downsampler = new FillingDownsampler(source, + BASE_TIME, BASE_TIME + (3600000 * 3), specification, 0, Long.MAX_VALUE); + + long ts = BASE_TIME; + double value = 6; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + ts += 3600000; + if (value == 6) { + value = 15; + } else { + value = Double.NaN; + } + } + + // hour offset by 30m + ((MockSeekableView)source).resetIndex(); + specification = new DownsamplingSpecification("1hc-sum-nan"); + specification.setTimezone(AF); + downsampler = new FillingDownsampler(source, 1356996600000L, + 1356996600000L + (3600000 * 4), specification, 0, Long.MAX_VALUE); + + ts = 1356996600000L; + value = 1; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + ts += 3600000; + if (value == 1) { + value = 9; + } else if (value == 9) { + value = 11; + } else { + value = Double.NaN; + } + } + + // multiple hours + ((MockSeekableView)source).resetIndex(); + specification = new DownsamplingSpecification("4hc-sum-nan"); + specification.setTimezone(AF); + downsampler = new FillingDownsampler(source, 1356996600000L, + 1356996600000L + (3600000 * 8), specification, 0, Long.MAX_VALUE); + + ts = 1356996600000L; + value = 21; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + ts = 1357011000000L; + value = Double.NaN; + } + } + + @Test + public void testDownsampler_calendarDay() { + source = SeekableViewsForTest.fromArray(new DataPoint[] { + MutableDataPoint.ofLongValue(DST_TS, 1), + MutableDataPoint.ofLongValue(DST_TS + 86399000, 2), + MutableDataPoint.ofLongValue(DST_TS + 126001000L, 3), // falls to the next in FJ + MutableDataPoint.ofLongValue(DST_TS + 172799000L, 4), + MutableDataPoint.ofLongValue(DST_TS + 172800000L, 5), + MutableDataPoint.ofLongValue(DST_TS + 242999000L, 6) // falls within 30m offset + }); + + // control + specification = new DownsamplingSpecification("1d-sum-nan"); + downsampler = new FillingDownsampler(source, DST_TS, + DST_TS + (86400000 * 4), specification, 0, Long.MAX_VALUE); + + long ts = DST_TS; + double value = 3; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + ts += 86400000; + if (value == 3) { + value = 7; + } else if (value == 7) { + value = 11; + } else { + value = Double.NaN; + } + } + + // 12 hour offset from UTC + ((MockSeekableView)source).resetIndex(); + specification = new DownsamplingSpecification("1dc-sum-nan"); + specification.setTimezone(TV); + downsampler = new FillingDownsampler(source, 1450094400000L - 86400000, + DST_TS + (86400000 * 5), specification, 0, Long.MAX_VALUE); + + ts = 1450094400000L - 86400000; // make sure we front-fill too + value = Double.NaN; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + ts += 86400000; + if (Double.isNaN(value)) { + value = 1; + } else if (value == 1) { + value = 5; + } else if (value == 5) { + value = 9; + } else if (value == 9) { + value = 6; + } else { + value = Double.NaN; + } + } + + // 11 hour offset from UTC + ((MockSeekableView)source).resetIndex(); + specification = new DownsamplingSpecification("1dc-sum-nan"); + specification.setTimezone(FJ); + downsampler = new FillingDownsampler(source, 1450094400000L, + DST_TS + (86400000 * 5), specification, 0, Long.MAX_VALUE); + + ts = 1450090800000L; + value = 1; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + ts += 86400000; + if (value == 1) { + value = 2; + } else if (value == 2) { + value = 12; + } else if (value == 12) { + value = 6; + } else { + value = Double.NaN; + } + } + + // 30m offset + ((MockSeekableView)source).resetIndex(); + specification = new DownsamplingSpecification("1dc-sum-nan"); + specification.setTimezone(AF); + downsampler = new FillingDownsampler(source, 1450121400000L, + DST_TS + (86400000 * 4), specification, 0, Long.MAX_VALUE); + + ts = 1450121400000L; + value = 1; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + ts += 86400000; + if (value == 1) { + value = 5; + } else if (value == 5) { + value = 15; + } else { + value = Double.NaN; + } + } + + // multiple days + ((MockSeekableView)source).resetIndex(); + specification = new DownsamplingSpecification("3dc-sum-nan"); + specification.setTimezone(AF); + downsampler = new FillingDownsampler(source, 1450121400000L, + DST_TS + (86400000 * 6), specification, 0, Long.MAX_VALUE); + + ts = 1450121400000L; + value = 21; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + ts += 86400000L * 3; + value = Double.NaN; + } + } + + @Test + public void testDownsampler_calendarWeek() { + source = SeekableViewsForTest.fromArray(new DataPoint[] { + MutableDataPoint.ofLongValue(DST_TS, 1), // a Tuesday in UTC land + MutableDataPoint.ofLongValue(DST_TS + (86400000L * 7), 2), + MutableDataPoint.ofLongValue(1451129400000L, 3), // falls to the next in FJ + MutableDataPoint.ofLongValue(DST_TS + (86400000L * 21), 4), + MutableDataPoint.ofLongValue(1452367799000L, 5) // falls within 30m offset + }); + // control + specification = new DownsamplingSpecification("1wc-sum-nan"); + downsampler = new FillingDownsampler(source, 1449964800000L, + DST_TS + (86400000L * 35), specification, 0, Long.MAX_VALUE); + + long ts = 1449964800000L; + double value = 1; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + ts += 86400000L * 7; + if (value == 1) { + value = 5; + } else if (value == 5) { + value = Double.NaN; + } else if (Double.isNaN(value)) { + value = 9; + } else { + value = Double.NaN; + } + } + + // 12 hour offset from UTC + ((MockSeekableView)source).resetIndex(); + specification = new DownsamplingSpecification("1wc-sum-nan"); + specification.setTimezone(TV); + downsampler = new FillingDownsampler(source, 1449964800000L, + DST_TS + (86400000L * 35), specification, 0, Long.MAX_VALUE); + + ts = 1449921600000L; + value = 1; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + ts += 86400000L * 7; + if (value == 1) { + value = 5; + } else if (value == 5) { + value = Double.NaN; + } else if (Double.isNaN(value)) { + value = 4; + } else { + value = 5; + } + } + + // 11 hour offset from UTC + ((MockSeekableView)source).resetIndex(); + specification = new DownsamplingSpecification("1wc-sum-nan"); + specification.setTimezone(FJ); + downsampler = new FillingDownsampler(source, 1449964800000L, + DST_TS + (86400000L * 35), specification, 0, Long.MAX_VALUE); + + ts = 1449918000000L; + value = 1; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + ts += 86400000L * 7; + value++; + } + // 30m offset + ((MockSeekableView)source).resetIndex(); + specification = new DownsamplingSpecification("1wc-sum-nan"); + specification.setTimezone(AF); + downsampler = new FillingDownsampler(source, 1449964800000L, + DST_TS + (86400000L * 35), specification, 0, Long.MAX_VALUE); + + ts = 1449948600000L; + value = 1; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + ts += 86400000L * 7; + if (value == 1) { + value = 5; + } else if (value == 5) { + value = Double.NaN; + } else if (Double.isNaN(value)) { + value = 9; + } else { + value = Double.NaN; + } + } + + // multiple weeks + ((MockSeekableView)source).resetIndex(); + specification = new DownsamplingSpecification("2wc-sum-nan"); + specification.setTimezone(AF); + downsampler = new FillingDownsampler(source, 1449964800000L, + DST_TS + (86400000L * 35), specification, 0, Long.MAX_VALUE); + + ts = 1449948600000L; + value = 6; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + ts += 86400000L * 14; + if (value == 6) { + value = 9; + } else { + value = Double.NaN; + } + } + } + + @Test + public void testDownsampler_calendarMonth() { + final long dec_1st = 1448928000000L; + source = spy(SeekableViewsForTest.fromArray(new DataPoint[] { + MutableDataPoint.ofLongValue(dec_1st, 1), + MutableDataPoint.ofLongValue(1451559600000L, 2), // falls to the next in FJ + MutableDataPoint.ofLongValue(1451606400000L, 3), // jan 1st + MutableDataPoint.ofLongValue(1454284800000L, 4), // feb 1st + MutableDataPoint.ofLongValue(1456704000000L, 5), // feb 29th (leap year) + MutableDataPoint.ofLongValue(1456772400000L, 6) // falls within 30m offset AF + })); + + // control + specification = new DownsamplingSpecification("1n-sum-nan"); + downsampler = new FillingDownsampler(source, dec_1st, + dec_1st + (2592000000L * 5), specification, 0, Long.MAX_VALUE); + + long ts = dec_1st; + double value = 1; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + ts += 2592000000L; + if (value == 1) { + value = 5; + } else if (value == 5) { + value = 4; + } else if (value == 4) { + value = 11; + } else { + value = Double.NaN; + } + } + + // 12h offset + ((MockSeekableView)source).resetIndex(); + specification = new DownsamplingSpecification("1nc-sum-nan"); + specification.setTimezone(TV); + downsampler = new FillingDownsampler(source, dec_1st, + dec_1st + (2592000000L * 6), specification, 0, Long.MAX_VALUE); + + ts = 1448884800000L; + value = 3; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + if (ts == 1448884800000L) { + ts = 1451563200000L; + } else if (ts == 1451563200000L) { + ts = 1454241600000L; + value = 9; + } else if (ts == 1454241600000L) { + ts = 1456747200000L; + value = 6; + } else { + ts = 1459425600000L; + value = Double.NaN; + } + } + + // 11h offset + ((MockSeekableView)source).resetIndex(); + specification = new DownsamplingSpecification("1nc-sum-nan"); + specification.setTimezone(FJ); + downsampler = new FillingDownsampler(source, dec_1st, + dec_1st + (2592000000L * 6), specification, 0, Long.MAX_VALUE); + + ts = 1448881200000L; + value = 1; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + if (ts == 1448881200000L) { + ts = 1451559600000L; + value = 5; + } else if (ts == 1451559600000L) { + ts = 1454241600000L; + value = 9; + } else if (ts == 1454241600000L) { + ts = 1456747200000L; + value = 6; + } else { + ts = 1459425600000L; + value = Double.NaN; + } + } + + // 30m offset + ((MockSeekableView)source).resetIndex(); + specification = new DownsamplingSpecification("1nc-sum-nan"); + specification.setTimezone(AF); + downsampler = new FillingDownsampler(source, dec_1st, + dec_1st + (2592000000L * 5), specification, 0, Long.MAX_VALUE); + + ts = 1448911800000L; + value = 3; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + if (ts == 1448911800000L) { + ts = 1451590200000L; + } else if (ts == 1451590200000L) { + ts = 1454268600000L; + value = 15; + } else { + ts = 1456774200000L; + value = Double.NaN; + } + } + + // multiple months + ((MockSeekableView)source).resetIndex(); + specification = new DownsamplingSpecification("3nc-sum-nan"); + specification.setTimezone(TV); + downsampler = new FillingDownsampler(source, dec_1st, + dec_1st + (2592000000L * 9), specification, 0, Long.MAX_VALUE); + + ts = 1443614400000L; + value = 3; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + if (ts == 1443614400000L) { + ts = 1451563200000L; + value = 18; + } else { + ts = 1459425600000L; + value = Double.NaN; + } + } + } + + @Test + public void testDownsampler_calendarSkipSomePoints() { + source = SeekableViewsForTest.fromArray(new DataPoint[] { + MutableDataPoint.ofLongValue(BASE_TIME, 1), + MutableDataPoint.ofLongValue(BASE_TIME + 1800000, 2), + // skip an hour + MutableDataPoint.ofLongValue(BASE_TIME + 7200000, 6) + }); + specification = new DownsamplingSpecification("1hc-sum-nan"); + specification.setTimezone(TV); + downsampler = new FillingDownsampler(source, 1356998400000L, 1357009200000L, + specification, 0, Long.MAX_VALUE); + + long ts = BASE_TIME; + double value = 3; + while (downsampler.hasNext()) { + DataPoint dp = downsampler.next(); + assertFalse(dp.isInteger()); + assertEquals(ts, dp.timestamp()); + assertEquals(value, dp.doubleValue(), 0.001); + ts += 3600000; + if (value == 3) { + value = Double.NaN; + } else { + value = 6; + } + } + } + + @Test + public void testDownsampler_noData() { + final SeekableView source = + SeekableViewsForTest.fromArray(new DataPoint[] { }); + specification = new DownsamplingSpecification("1m-sum-nan"); + final Downsampler downsampler = new FillingDownsampler(source, BASE_TIME, + BASE_TIME + 60000L * 2L, specification, 0, 0); + + long timestamp = 1356998400000L; + step(downsampler, timestamp, Double.NaN); + step(downsampler, timestamp += 60000, Double.NaN); + assertFalse(downsampler.hasNext()); + } + + @Test + public void testDownsampler_noDataCalendar() { + final SeekableView source = + SeekableViewsForTest.fromArray(new DataPoint[] { }); + specification = new DownsamplingSpecification("1mc-sum-nan"); + final Downsampler downsampler = new FillingDownsampler(source, BASE_TIME, + BASE_TIME + 60000L * 2L, specification, 0, 0); + + long timestamp = 1356998400000L; + step(downsampler, timestamp, Double.NaN); + step(downsampler, timestamp += 60000, Double.NaN); assertFalse(downsampler.hasNext()); } diff --git a/test/core/TestTSQuery.java b/test/core/TestTSQuery.java index 9c1fdf90dd..d528eaaf56 100644 --- a/test/core/TestTSQuery.java +++ b/test/core/TestTSQuery.java @@ -15,6 +15,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.when; @@ -51,6 +52,51 @@ public void validate() { assertEquals(Aggregators.SUM, q.getQueries().get(0).aggregator()); assertEquals(Aggregators.AVG, q.getQueries().get(0).downsampler()); assertEquals(300000, q.getQueries().get(0).downsampleInterval()); + assertNull(q.getTimezone()); + assertEquals("UTC", q.getQueries().get(0).downsamplingSpecification() + .getTimezone().getID()); + assertFalse(q.getQueries().get(0).downsamplingSpecification().useCalendar()); + } + + @Test + public void validateWithTimezone() { + TSQuery q = this.getMetricForValidate(); + q.setUseCalendar(true); + q.setTimezone("Pacific/Funafuti"); + q.validateAndSetQuery(); + assertEquals(1356998400000L, q.startTime()); + assertEquals(1356998460000L, q.endTime()); + assertEquals("sys.cpu.0", q.getQueries().get(0).getMetric()); + assertEquals("wildcard(*)", q.getQueries().get(0).getTags().get("host")); + assertEquals("literal_or(lga)", q.getQueries().get(0).getTags().get("dc")); + assertEquals(Aggregators.SUM, q.getQueries().get(0).aggregator()); + assertEquals(Aggregators.AVG, q.getQueries().get(0).downsampler()); + assertEquals(300000, q.getQueries().get(0).downsampleInterval()); + assertEquals("Pacific/Funafuti", q.getTimezone()); + assertEquals("Pacific/Funafuti", q.getQueries().get(0).downsamplingSpecification() + .getTimezone().getID()); + assertTrue(q.getQueries().get(0).downsamplingSpecification().useCalendar()); + } + + @Test + public void validateVerifyNoDSOverrideWithCalendar() { + TSQuery q = this.getMetricForValidate(); + q.setUseCalendar(true); + q.setTimezone("Pacific/Funafuti"); + q.getQueries().get(0).setDownsample(null); + q.validateAndSetQuery(); + assertEquals(1356998400000L, q.startTime()); + assertEquals(1356998460000L, q.endTime()); + assertEquals("sys.cpu.0", q.getQueries().get(0).getMetric()); + assertEquals("wildcard(*)", q.getQueries().get(0).getTags().get("host")); + assertEquals("literal_or(lga)", q.getQueries().get(0).getTags().get("dc")); + assertEquals(Aggregators.SUM, q.getQueries().get(0).aggregator()); + assertNull(q.getQueries().get(0).downsampler()); + assertEquals(0, q.getQueries().get(0).downsampleInterval()); + assertEquals("Pacific/Funafuti", q.getTimezone()); + assertEquals("UTC", q.getQueries().get(0).downsamplingSpecification() + .getTimezone().getID()); + assertFalse(q.getQueries().get(0).downsamplingSpecification().useCalendar()); } @Test (expected = IllegalArgumentException.class) @@ -241,6 +287,25 @@ public void testHashCodeandEqualsTimezoneInvalid() throws Exception { assertFalse(sub1 == sub2); } + @Test + public void testHashCodeandEqualsUseCalendar() { + TSQuery sub1 = getMetricForValidate(); + + final int hash_a = sub1.hashCode(); + sub1.setUseCalendar(true); + final int hash_b = sub1.hashCode(); + assertTrue(hash_a != hash_b); + sub1.validateAndSetQuery(); + assertEquals(hash_b, sub1.hashCode()); + + TSQuery sub2 = getMetricForValidate(); + sub2.setUseCalendar(true); + + assertEquals(hash_b, sub2.hashCode()); + assertEquals(sub1, sub2); + assertFalse(sub1 == sub2); + } + @Test public void testHashCodeandEqualsOptions() { TSQuery sub1 = getMetricForValidate(); diff --git a/test/utils/TestDateTime.java b/test/utils/TestDateTime.java index 63ae2ffab8..07facd6567 100644 --- a/test/utils/TestDateTime.java +++ b/test/utils/TestDateTime.java @@ -21,19 +21,39 @@ import static org.mockito.Mockito.when; import java.text.SimpleDateFormat; +import java.util.Calendar; import java.util.TimeZone; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; +//"Classloader hell"... It's real. Tell PowerMock to ignore these classes +//because they fiddle with the class loader. We don't test them anyway. +@PowerMockIgnore({"javax.management.*", "javax.xml.*", + "ch.qos.*", "org.slf4j.*", + "com.sum.*", "org.xml.*"}) @RunWith(PowerMockRunner.class) @PrepareForTest({ DateTime.class, System.class }) public final class TestDateTime { + //30 minute offset + final static TimeZone AF = DateTime.timezones.get("Asia/Kabul"); + // 45 minute offset w DST + final static TimeZone NZ = DateTime.timezones.get("Pacific/Chatham"); + // 12h offset w/o DST + final static TimeZone TV = DateTime.timezones.get("Pacific/Funafuti"); + // 12h offset w DST + final static TimeZone FJ = DateTime.timezones.get("Pacific/Fiji"); + // Fri, 15 May 2015 14:21:13.432 UTC + final static long NON_DST_TS = 1431699673432L; + // Tue, 15 Dec 2015 04:02:25.123 UTC + final static long DST_TS = 1450152145123L; + @Before public void before() { PowerMockito.mockStatic(System.class); @@ -380,6 +400,474 @@ public void nanoTime() { assertEquals(1388534400000000000L, DateTime.nanoTime()); } + @Test + public void previousIntervalMilliseconds() { + // interval 1 + assertEquals(DST_TS, DateTime.previousInterval(DST_TS, + 1, Calendar.MILLISECOND).getTimeInMillis()); + assertEquals(NON_DST_TS, DateTime.previousInterval(NON_DST_TS, + 1, Calendar.MILLISECOND).getTimeInMillis()); + + // interval 100 + assertEquals(1450152145100L, DateTime.previousInterval(DST_TS, + 100, Calendar.MILLISECOND).getTimeInMillis()); + assertEquals(1450152145000L, DateTime.previousInterval(1450152145000L, + 100, Calendar.MILLISECOND).getTimeInMillis()); + + // odd interval + assertEquals(1450152144769L, DateTime.previousInterval(DST_TS, + 799, Calendar.MILLISECOND).getTimeInMillis()); + + // TZs - all the same for ms + assertEquals(1450152145100L, DateTime.previousInterval(DST_TS, + 100, Calendar.MILLISECOND, AF).getTimeInMillis()); + assertEquals(1431699673400L, DateTime.previousInterval(NON_DST_TS, + 100, Calendar.MILLISECOND, AF).getTimeInMillis()); + assertEquals(1450152145100L, DateTime.previousInterval(DST_TS, + 100, Calendar.MILLISECOND, NZ).getTimeInMillis()); + assertEquals(1431699673400L, DateTime.previousInterval(NON_DST_TS, + 100, Calendar.MILLISECOND, NZ).getTimeInMillis()); + assertEquals(1450152145100L, DateTime.previousInterval(DST_TS, + 100, Calendar.MILLISECOND, TV).getTimeInMillis()); + assertEquals(1431699673400L, DateTime.previousInterval(NON_DST_TS, + 100, Calendar.MILLISECOND, TV).getTimeInMillis()); + assertEquals(1450152145100L, DateTime.previousInterval(DST_TS, + 100, Calendar.MILLISECOND, FJ).getTimeInMillis()); + assertEquals(1431699673400L, DateTime.previousInterval(NON_DST_TS, + 100, Calendar.MILLISECOND, FJ).getTimeInMillis()); + + // multiples + assertEquals(1450152120000L, DateTime.previousInterval(DST_TS, + 60000, Calendar.MILLISECOND).getTimeInMillis()); + assertEquals(1431699660000L, DateTime.previousInterval(NON_DST_TS, + 60000, Calendar.MILLISECOND).getTimeInMillis()); + } + + @Test + public void previousIntervalSeconds() { + // interval 1 + assertEquals(1450152145000L, DateTime.previousInterval(DST_TS, + 1, Calendar.SECOND).getTimeInMillis()); + assertEquals(1431699673000L, DateTime.previousInterval(NON_DST_TS, + 1, Calendar.SECOND).getTimeInMillis()); + + // interval 30 + assertEquals(1450152120000L, DateTime.previousInterval(DST_TS, + 30, Calendar.SECOND).getTimeInMillis()); + assertEquals(1431699660000L, DateTime.previousInterval(NON_DST_TS, + 30, Calendar.SECOND).getTimeInMillis()); + assertEquals(1450152120000L, DateTime.previousInterval(1450152120000L, + 30, Calendar.SECOND).getTimeInMillis()); + + // odd interval + assertEquals(1431699647000L, DateTime.previousInterval(NON_DST_TS, + 29, Calendar.SECOND).getTimeInMillis()); + assertEquals(1450152145000L, DateTime.previousInterval(DST_TS, + 29, Calendar.SECOND).getTimeInMillis()); + + // TZs - all the same for seconds + assertEquals(1450152120000L, DateTime.previousInterval(DST_TS, + 30, Calendar.SECOND, AF).getTimeInMillis()); + assertEquals(1431699660000L, DateTime.previousInterval(NON_DST_TS, + 30, Calendar.SECOND, AF).getTimeInMillis()); + assertEquals(1450152120000L, DateTime.previousInterval(DST_TS, + 30, Calendar.SECOND, NZ).getTimeInMillis()); + assertEquals(1431699660000L, DateTime.previousInterval(NON_DST_TS, + 30, Calendar.SECOND, NZ).getTimeInMillis()); + assertEquals(1450152120000L, DateTime.previousInterval(DST_TS, + 30, Calendar.SECOND, TV).getTimeInMillis()); + assertEquals(1431699660000L, DateTime.previousInterval(NON_DST_TS, + 30, Calendar.SECOND, TV).getTimeInMillis()); + assertEquals(1450152120000L, DateTime.previousInterval(DST_TS, + 30, Calendar.SECOND, FJ).getTimeInMillis()); + assertEquals(1431699660000L, DateTime.previousInterval(NON_DST_TS, + 30, Calendar.SECOND, FJ).getTimeInMillis()); + + // multiples + assertEquals(1450152000000L, DateTime.previousInterval(DST_TS, + 60000, Calendar.SECOND).getTimeInMillis()); + assertEquals(1431698400000L, DateTime.previousInterval(NON_DST_TS, + 60000, Calendar.SECOND).getTimeInMillis()); + } + + @Test + public void previousIntervalMinutes() { + // interval 1 + assertEquals(1450152120000L, DateTime.previousInterval(DST_TS, + 1, Calendar.MINUTE).getTimeInMillis()); + assertEquals(1431699660000L, DateTime.previousInterval(NON_DST_TS, + 1, Calendar.MINUTE).getTimeInMillis()); + + // interval 30 + assertEquals(1450152000000L, DateTime.previousInterval(DST_TS, + 30, Calendar.MINUTE).getTimeInMillis()); + assertEquals(1431698400000L, DateTime.previousInterval(NON_DST_TS, + 30, Calendar.MINUTE).getTimeInMillis()); + assertEquals(1431698400000L, DateTime.previousInterval(1431698400000L, + 30, Calendar.MINUTE).getTimeInMillis()); + + // odd interval + assertEquals(1431698460000L, DateTime.previousInterval(NON_DST_TS, + 29, Calendar.MINUTE).getTimeInMillis()); + assertEquals(1450151520000L, DateTime.previousInterval(DST_TS, + 29, Calendar.MINUTE).getTimeInMillis()); + + // TZs + assertEquals(1450152000000L, DateTime.previousInterval(DST_TS, + 30, Calendar.MINUTE, AF).getTimeInMillis()); + assertEquals(1431698400000L, DateTime.previousInterval(NON_DST_TS, + 30, Calendar.MINUTE, AF).getTimeInMillis()); + // 15 min diff + assertEquals(1450152000000L, DateTime.previousInterval(DST_TS, + 15, Calendar.MINUTE, AF).getTimeInMillis()); + assertEquals(1431699300000L, DateTime.previousInterval(NON_DST_TS, + 15, Calendar.MINUTE, AF).getTimeInMillis()); + // outliers @ 45 minutes + assertEquals(1450151100000L, DateTime.previousInterval(DST_TS, + 30, Calendar.MINUTE, NZ).getTimeInMillis()); + assertEquals(1431699300000L, DateTime.previousInterval(NON_DST_TS, + 30, Calendar.MINUTE, NZ).getTimeInMillis()); + // back to normal + assertEquals(1450152000000L, DateTime.previousInterval(DST_TS, + 30, Calendar.MINUTE, TV).getTimeInMillis()); + assertEquals(1431698400000L, DateTime.previousInterval(NON_DST_TS, + 30, Calendar.MINUTE, TV).getTimeInMillis()); + assertEquals(1450152000000L, DateTime.previousInterval(DST_TS, + 30, Calendar.MINUTE, FJ).getTimeInMillis()); + assertEquals(1431698400000L, DateTime.previousInterval(NON_DST_TS, + 30, Calendar.MINUTE, FJ).getTimeInMillis()); + + // multiples + assertEquals(1450152000000L, DateTime.previousInterval(DST_TS, + 120, Calendar.MINUTE).getTimeInMillis()); + assertEquals(1431698400000L, DateTime.previousInterval(NON_DST_TS, + 120, Calendar.MINUTE).getTimeInMillis()); + } + + @Test + public void previousIntervalHours() { + // interval 1 + assertEquals(1450152000000L, DateTime.previousInterval(DST_TS, + 1, Calendar.HOUR_OF_DAY).getTimeInMillis()); + assertEquals(1431698400000L, DateTime.previousInterval(NON_DST_TS, + 1, Calendar.HOUR_OF_DAY).getTimeInMillis()); + + // interval 12 + assertEquals(1450137600000L, DateTime.previousInterval(DST_TS, + 12, Calendar.HOUR_OF_DAY).getTimeInMillis()); + assertEquals(1431691200000L, DateTime.previousInterval(NON_DST_TS, + 12, Calendar.HOUR_OF_DAY).getTimeInMillis()); + assertEquals(1450137600000L, DateTime.previousInterval(1450137600000L, + 12, Calendar.HOUR_OF_DAY).getTimeInMillis()); + + // odd interval + assertEquals(1431680400000L, DateTime.previousInterval(NON_DST_TS, + 15, Calendar.HOUR_OF_DAY).getTimeInMillis()); + assertEquals(1450116000000L, DateTime.previousInterval(DST_TS, + 15, Calendar.HOUR_OF_DAY).getTimeInMillis()); + + // TZs - 30m offset here + assertEquals(1450121400000L, DateTime.previousInterval(DST_TS, + 12, Calendar.HOUR_OF_DAY, AF).getTimeInMillis()); + assertEquals(1431675000000L, DateTime.previousInterval(NON_DST_TS, + 12, Calendar.HOUR_OF_DAY, AF).getTimeInMillis()); + // outliers @ 45 minutes + assertEquals(1450131300000L, DateTime.previousInterval(DST_TS, + 12, Calendar.HOUR_OF_DAY, NZ).getTimeInMillis()); + assertEquals(1431688500000L, DateTime.previousInterval(NON_DST_TS, + 12, Calendar.HOUR_OF_DAY, NZ).getTimeInMillis()); + // back to normal + assertEquals(1450137600000L, DateTime.previousInterval(DST_TS, + 12, Calendar.HOUR_OF_DAY, TV).getTimeInMillis()); + assertEquals(1431691200000L, DateTime.previousInterval(NON_DST_TS, + 12, Calendar.HOUR_OF_DAY, TV).getTimeInMillis()); + assertEquals(1450134000000L, DateTime.previousInterval(DST_TS, + 12, Calendar.HOUR_OF_DAY, FJ).getTimeInMillis()); + assertEquals(1431691200000L, DateTime.previousInterval(NON_DST_TS, + 12, Calendar.HOUR_OF_DAY, FJ).getTimeInMillis()); + + // multiples + assertEquals(1450094400000L, DateTime.previousInterval(DST_TS, + 36, Calendar.HOUR_OF_DAY).getTimeInMillis()); + assertEquals(1431604800000L, DateTime.previousInterval(NON_DST_TS, + 36, Calendar.HOUR_OF_DAY).getTimeInMillis()); + } + + @Test + public void previousIntervalDays() { + // interval 1 + assertEquals(1450137600000L, DateTime.previousInterval(DST_TS, + 1, Calendar.DAY_OF_MONTH).getTimeInMillis()); + assertEquals(1431648000000L, DateTime.previousInterval(NON_DST_TS, + 1, Calendar.DAY_OF_MONTH).getTimeInMillis()); + + // interval 7 - since days aren't consistent, the only thing we can + // do is pick a starting day, i.e. start of the year + assertEquals(1449705600000L, DateTime.previousInterval(DST_TS, + 7, Calendar.DAY_OF_MONTH).getTimeInMillis()); + assertEquals(1431561600000L, DateTime.previousInterval(NON_DST_TS, + 7, Calendar.DAY_OF_MONTH).getTimeInMillis()); + assertEquals(1449705600000L, DateTime.previousInterval(1449705600000L, + 7, Calendar.DAY_OF_MONTH).getTimeInMillis()); + + // leap year + assertEquals(1330473600000L, DateTime.previousInterval(1330516800000L, + 1, Calendar.DAY_OF_MONTH).getTimeInMillis()); + + // TZs - 30m offset here + assertEquals(1450121400000L, DateTime.previousInterval(DST_TS, + 1, Calendar.DAY_OF_MONTH, AF).getTimeInMillis()); + assertEquals(1431631800000L, DateTime.previousInterval(NON_DST_TS, + 1, Calendar.DAY_OF_MONTH, AF).getTimeInMillis()); + // outliers @ 45 minutes + assertEquals(1450088100000L, DateTime.previousInterval(DST_TS, + 1, Calendar.DAY_OF_MONTH, NZ).getTimeInMillis()); + assertEquals(1431688500000L, DateTime.previousInterval(NON_DST_TS, + 1, Calendar.DAY_OF_MONTH, NZ).getTimeInMillis()); + // back to normal + assertEquals(1450094400000L, DateTime.previousInterval(DST_TS, + 1, Calendar.DAY_OF_MONTH, TV).getTimeInMillis()); + assertEquals(1431691200000L, DateTime.previousInterval(NON_DST_TS, + 1, Calendar.DAY_OF_MONTH, TV).getTimeInMillis()); + assertEquals(1450090800000L, DateTime.previousInterval(DST_TS, + 1, Calendar.DAY_OF_MONTH, FJ).getTimeInMillis()); + assertEquals(1431691200000L, DateTime.previousInterval(NON_DST_TS, + 1, Calendar.DAY_OF_MONTH, FJ).getTimeInMillis()); + + // multiples + assertEquals(1445990400000L, DateTime.previousInterval(DST_TS, + 60, Calendar.DAY_OF_MONTH).getTimeInMillis()); + assertEquals(1430438400000L, DateTime.previousInterval(NON_DST_TS, + 60, Calendar.DAY_OF_MONTH).getTimeInMillis()); + } + + @Test + public void previousIntervalWeeks() { + // interval 1 DST_TS starts on 13th of Dec, NON starts on the 10th of May + assertEquals(1449964800000L, DateTime.previousInterval(DST_TS, + 1, Calendar.DAY_OF_WEEK).getTimeInMillis()); + assertEquals(1431216000000L, DateTime.previousInterval(NON_DST_TS, + 1, Calendar.DAY_OF_WEEK).getTimeInMillis()); + + // interval 2 + assertEquals(1449964800000L, DateTime.previousInterval(DST_TS, + 2, Calendar.DAY_OF_WEEK).getTimeInMillis()); + assertEquals(1431216000000L, DateTime.previousInterval(NON_DST_TS, + 2, Calendar.DAY_OF_WEEK).getTimeInMillis()); + assertEquals(1435449600000L, DateTime.previousInterval(1435795200000L, + 2, Calendar.DAY_OF_WEEK).getTimeInMillis()); + + // TZs - 30m offset here + assertEquals(1449948600000L, DateTime.previousInterval(DST_TS, + 1, Calendar.DAY_OF_WEEK, AF).getTimeInMillis()); + assertEquals(1431199800000L, DateTime.previousInterval(NON_DST_TS, + 1, Calendar.DAY_OF_WEEK, AF).getTimeInMillis()); + // outliers @ 45 minutes + assertEquals(1449915300000L, DateTime.previousInterval(DST_TS, + 1, Calendar.DAY_OF_WEEK, NZ).getTimeInMillis()); + assertEquals(1431170100000L, DateTime.previousInterval(NON_DST_TS, + 1, Calendar.DAY_OF_WEEK, NZ).getTimeInMillis()); + // back to normal + assertEquals(1449921600000L, DateTime.previousInterval(DST_TS, + 1, Calendar.DAY_OF_WEEK, TV).getTimeInMillis()); + assertEquals(1431172800000L, DateTime.previousInterval(NON_DST_TS, + 1, Calendar.DAY_OF_WEEK, TV).getTimeInMillis()); + assertEquals(1449918000000L, DateTime.previousInterval(DST_TS, + 1, Calendar.DAY_OF_WEEK, FJ).getTimeInMillis()); + assertEquals(1431172800000L, DateTime.previousInterval(NON_DST_TS, + 1, Calendar.DAY_OF_WEEK, FJ).getTimeInMillis()); + + // multiples - still from start of the week + assertEquals(1449964800000L, DateTime.previousInterval(DST_TS, + 104, Calendar.DAY_OF_WEEK).getTimeInMillis()); + assertEquals(1431216000000L, DateTime.previousInterval(NON_DST_TS, + 104, Calendar.DAY_OF_WEEK).getTimeInMillis()); + } + + @Test + public void previousIntervalWeekOfYear() { + // interval 1 DST_TS starts on 10th of Dec, NON starts on the 14th of May + assertEquals(1449705600000L, DateTime.previousInterval(DST_TS, + 1, Calendar.WEEK_OF_YEAR).getTimeInMillis()); + assertEquals(1431561600000L, DateTime.previousInterval(NON_DST_TS, + 1, Calendar.WEEK_OF_YEAR).getTimeInMillis()); + + // interval 26 + assertEquals(1435795200000L, DateTime.previousInterval(DST_TS, + 26, Calendar.WEEK_OF_YEAR).getTimeInMillis()); + assertEquals(1420070400000L, DateTime.previousInterval(NON_DST_TS, + 26, Calendar.WEEK_OF_YEAR).getTimeInMillis()); + assertEquals(1435795200000L, DateTime.previousInterval(1435795200000L, + 26, Calendar.WEEK_OF_YEAR).getTimeInMillis()); + + // TZs - 30m offset here + assertEquals(1449689400000L, DateTime.previousInterval(DST_TS, + 1, Calendar.WEEK_OF_YEAR, AF).getTimeInMillis()); + assertEquals(1431545400000L, DateTime.previousInterval(NON_DST_TS, + 1, Calendar.WEEK_OF_YEAR, AF).getTimeInMillis()); + // outliers @ 45 minutes + assertEquals(1449656100000L, DateTime.previousInterval(DST_TS, + 1, Calendar.WEEK_OF_YEAR, NZ).getTimeInMillis()); + assertEquals(1431515700000L, DateTime.previousInterval(NON_DST_TS, + 1, Calendar.WEEK_OF_YEAR, NZ).getTimeInMillis()); + // back to normal + assertEquals(1449662400000L, DateTime.previousInterval(DST_TS, + 1, Calendar.WEEK_OF_YEAR, TV).getTimeInMillis()); + assertEquals(1431518400000L, DateTime.previousInterval(NON_DST_TS, + 1, Calendar.WEEK_OF_YEAR, TV).getTimeInMillis()); + assertEquals(1449658800000L, DateTime.previousInterval(DST_TS, + 1, Calendar.WEEK_OF_YEAR, FJ).getTimeInMillis()); + assertEquals(1431518400000L, DateTime.previousInterval(NON_DST_TS, + 1, Calendar.WEEK_OF_YEAR, FJ).getTimeInMillis()); + + // multiples + assertEquals(1420070400000L, DateTime.previousInterval(DST_TS, + 104, Calendar.WEEK_OF_YEAR).getTimeInMillis()); + assertEquals(1420070400000L, DateTime.previousInterval(NON_DST_TS, + 104, Calendar.WEEK_OF_YEAR).getTimeInMillis()); + } + + @Test + public void previousIntervalMonths() { + // interval 1 + assertEquals(1448928000000L, DateTime.previousInterval(DST_TS, + 1, Calendar.MONTH).getTimeInMillis()); + assertEquals(1430438400000L, DateTime.previousInterval(NON_DST_TS, + 1, Calendar.MONTH).getTimeInMillis()); + + // interval 3 (quarters) + assertEquals(1443657600000L, DateTime.previousInterval(DST_TS, + 3, Calendar.MONTH).getTimeInMillis()); + assertEquals(1427846400000L, DateTime.previousInterval(NON_DST_TS, + 3, Calendar.MONTH).getTimeInMillis()); + assertEquals(1443657600000L, DateTime.previousInterval(1443657600000L, + 3, Calendar.MONTH).getTimeInMillis()); + + // odd intervals + assertEquals(1446336000000L, DateTime.previousInterval(DST_TS, + 5, Calendar.MONTH).getTimeInMillis()); + assertEquals(1420070400000L, DateTime.previousInterval(NON_DST_TS, + 5, Calendar.MONTH).getTimeInMillis()); + + // TZs - 30m offset here + assertEquals(1448911800000L, DateTime.previousInterval(DST_TS, + 1, Calendar.MONTH, AF).getTimeInMillis()); + assertEquals(1430422200000L, DateTime.previousInterval(NON_DST_TS, + 1, Calendar.MONTH, AF).getTimeInMillis()); + // outliers @ 45 minutes + assertEquals(1448878500000L, DateTime.previousInterval(DST_TS, + 1, Calendar.MONTH, NZ).getTimeInMillis()); + assertEquals(1430392500000L, DateTime.previousInterval(NON_DST_TS, + 1, Calendar.MONTH, NZ).getTimeInMillis()); + // back to normal + assertEquals(1448884800000L, DateTime.previousInterval(DST_TS, + 1, Calendar.MONTH, TV).getTimeInMillis()); + assertEquals(1430395200000L, DateTime.previousInterval(NON_DST_TS, + 1, Calendar.MONTH, TV).getTimeInMillis()); + assertEquals(1448881200000L, DateTime.previousInterval(DST_TS, + 1, Calendar.MONTH, FJ).getTimeInMillis()); + assertEquals(1430395200000L, DateTime.previousInterval(NON_DST_TS, + 1, Calendar.MONTH, FJ).getTimeInMillis()); + + // multiples + assertEquals(1420070400000L, DateTime.previousInterval(DST_TS, + 24, Calendar.MONTH).getTimeInMillis()); + assertEquals(1420070400000L, DateTime.previousInterval(NON_DST_TS, + 24, Calendar.MONTH).getTimeInMillis()); + } + + @Test + public void previousIntervalYears() { + // interval 1 + assertEquals(1420070400000L, DateTime.previousInterval(DST_TS, + 1, Calendar.YEAR).getTimeInMillis()); + assertEquals(1420070400000L, DateTime.previousInterval(NON_DST_TS, + 1, Calendar.YEAR).getTimeInMillis()); + + // interval 5 + assertEquals(1420070400000L, DateTime.previousInterval(DST_TS, + 5, Calendar.YEAR).getTimeInMillis()); + assertEquals(1420070400000L, DateTime.previousInterval(NON_DST_TS, + 5, Calendar.YEAR).getTimeInMillis()); + assertEquals(1420070400000L, DateTime.previousInterval(1420070400000L, + 5, Calendar.YEAR).getTimeInMillis()); + + // TZs - 30m offset here + assertEquals(1420054200000L, DateTime.previousInterval(DST_TS, + 1, Calendar.YEAR, AF).getTimeInMillis()); + assertEquals(1420054200000L, DateTime.previousInterval(NON_DST_TS, + 1, Calendar.YEAR, AF).getTimeInMillis()); + // outliers @ 45 minutes + assertEquals(1420020900000L, DateTime.previousInterval(DST_TS, + 1, Calendar.YEAR, NZ).getTimeInMillis()); + assertEquals(1420020900000L, DateTime.previousInterval(NON_DST_TS, + 1, Calendar.YEAR, NZ).getTimeInMillis()); + // back to normal + assertEquals(1420027200000L, DateTime.previousInterval(DST_TS, + 1, Calendar.YEAR, TV).getTimeInMillis()); + assertEquals(1420027200000L, DateTime.previousInterval(NON_DST_TS, + 1, Calendar.YEAR, TV).getTimeInMillis()); + assertEquals(1420023600000L, DateTime.previousInterval(DST_TS, + 1, Calendar.YEAR, FJ).getTimeInMillis()); + assertEquals(1420023600000L, DateTime.previousInterval(NON_DST_TS, + 1, Calendar.YEAR, FJ).getTimeInMillis()); + } + + @Test (expected = IllegalArgumentException.class) + public void previousIntervalNegativeTs() { + DateTime.previousInterval(-42, 1, Calendar.MINUTE); + } + + @Test (expected = IllegalArgumentException.class) + public void previousIntervalNegativeInterval() { + DateTime.previousInterval(1355961600000L, -1, Calendar.MINUTE); + } + + @Test (expected = IllegalArgumentException.class) + public void previousIntervalZeroInterval() { + DateTime.previousInterval(1355961600000L, 0, Calendar.MINUTE); + } + + @Test (expected = IllegalArgumentException.class) + public void previousIntervalNegativeUnit() { + DateTime.previousInterval(1355961600000L, 1, -1); + } + + @Test (expected = IllegalArgumentException.class) + public void previousIntervalUnsupportedUnit() { + DateTime.previousInterval(1355961600000L, 1, Calendar.HOUR); + } + + @Test (expected = IllegalArgumentException.class) + public void previousIntervalMassiveUnit() { + DateTime.previousInterval(1355961600000L, 1, 6048); + } + + @Test + public void unitsToCalendarType() { + assertEquals(Calendar.MILLISECOND, DateTime.unitsToCalendarType("ms")); + assertEquals(Calendar.SECOND, DateTime.unitsToCalendarType("s")); + assertEquals(Calendar.MINUTE, DateTime.unitsToCalendarType("m")); + assertEquals(Calendar.HOUR_OF_DAY, DateTime.unitsToCalendarType("h")); + assertEquals(Calendar.DAY_OF_MONTH, DateTime.unitsToCalendarType("d")); + assertEquals(Calendar.DAY_OF_WEEK, DateTime.unitsToCalendarType("w")); + assertEquals(Calendar.MONTH, DateTime.unitsToCalendarType("n")); + assertEquals(Calendar.YEAR, DateTime.unitsToCalendarType("y")); + try { + DateTime.unitsToCalendarType("j"); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { } + try { + DateTime.unitsToCalendarType(null); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { } + try { + DateTime.unitsToCalendarType(""); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { } + } + + @Test public void msFromNano() { assertEquals(0, DateTime.msFromNano(0), 0.0001); From c7dee0184bbc89aa90b7688c6224ca6cf3a2430d Mon Sep 17 00:00:00 2001 From: Jonathan Creasy Date: Fri, 4 Mar 2016 17:53:03 -0600 Subject: [PATCH 137/233] Added timeShift function Fixes #175 Signed-off-by: Chris Larsen --- Makefile.am | 1 + src/query/expression/ExpressionFactory.java | 2 + src/query/expression/TimeShift.java | 135 ++++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 src/query/expression/TimeShift.java diff --git a/Makefile.am b/Makefile.am index 78abae4d5d..b569d7913b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -96,6 +96,7 @@ tsdb_SRC := \ src/query/expression/PostAggregatedDataPoints.java \ src/query/expression/Scale.java \ src/query/expression/SumSeries.java \ + src/query/expression/TimeShift.java \ src/query/expression/TimeSyncedIterator.java \ src/query/expression/UnionIterator.java \ src/query/expression/VariableIterator.java \ diff --git a/src/query/expression/ExpressionFactory.java b/src/query/expression/ExpressionFactory.java index 23bd39d1dd..e0fbdd44e8 100644 --- a/src/query/expression/ExpressionFactory.java +++ b/src/query/expression/ExpressionFactory.java @@ -35,6 +35,8 @@ public final class ExpressionFactory { available_functions.put("movingAverage", new MovingAverage()); available_functions.put("highestCurrent", new HighestCurrent()); available_functions.put("highestMax", new HighestMax()); + available_functions.put("shift", new TimeShift()); + available_functions.put("timeShift", new TimeShift()); } /** Don't instantiate me! */ diff --git a/src/query/expression/TimeShift.java b/src/query/expression/TimeShift.java new file mode 100644 index 0000000000..876175d64a --- /dev/null +++ b/src/query/expression/TimeShift.java @@ -0,0 +1,135 @@ +package net.opentsdb.query.expression; +/** + * Copyright 2015 The opentsdb Authors + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import net.opentsdb.core.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class TimeShift implements Expression { + /** + * in place modify of TsdbResult array to increase timestamps by timeshift + * @param data_query + * @param results + * @param params + * @return + */ + @Override + public DataPoints[] evaluate(TSQuery data_query, List results, List params) { + //not 100% sure what to do here -> do I need to think of the case where I have no data points + if(results == null || results.isEmpty()) { + return new DataPoints[]{}; + } + if(params == null || results.isEmpty()) { + throw new IllegalArgumentException("Need amount of timeshift to perform timeshift"); + } + + String param = params.get(0); + if (param == null || param.length() == 0) { + throw new IllegalArgumentException("Invalid timeshift='" + param + "'"); + } + + param = param.trim(); + + long timeshift = -1; + if (param.startsWith("'") && param.endsWith("'")) { + timeshift = parseParam(param) / 1000; + } else { + throw new RuntimeException("Invalid timeshift parameter: eg '10min'"); + } + + if (timeshift <= 0) { + throw new RuntimeException("timeshift <= 0"); + } + + DataPoints[] inputPoints = results.get(0); + DataPoints[] outputPoints = new DataPoints[inputPoints.length]; + for(int n = 0; n < inputPoints.length; n++) { + outputPoints[n] = shift(inputPoints[n], timeshift); + } + return outputPoints; + } + + public static long parseParam(String param) { + char[] chars = param.toCharArray(); + int tuIndex = 0; + for (int c = 1; c < chars.length; c++) { + if (Character.isDigit(chars[c])) { + tuIndex++; + } else { + break; + } + } + + if (tuIndex == 0) { + throw new RuntimeException("Invalid Parameter: " + param); + } + + int time = Integer.parseInt(param.substring(1, tuIndex + 1)); + String unit = param.substring(tuIndex + 1, param.length() - 1); + if ("sec".equals(unit)) { + return TimeUnit.MILLISECONDS.convert(time, TimeUnit.SECONDS); + } else if ("min".equals(unit)) { + return TimeUnit.MILLISECONDS.convert(time, TimeUnit.MINUTES); + } else if ("hr".equals(unit)) { + return TimeUnit.MILLISECONDS.convert(time, TimeUnit.HOURS); + } else if ("day".equals(unit) || "days".equals(unit)) { + return TimeUnit.MILLISECONDS.convert(time, TimeUnit.DAYS); + } else if ("week".equals(unit) || "weeks".equals(unit)) { + //didn't have week so small cheat here + return TimeUnit.MILLISECONDS.convert(time*7, TimeUnit.DAYS); + } + else { + throw new RuntimeException("unknown time unit=" + unit); + } + } + + /** + * Adjusts the timestamp of each datapoint by timeshift + * @param points The data points to factor + * @param timeshift The factor to multiply by + * @return The resulting data points + */ + private DataPoints shift(final DataPoints points, final long timeshift) { + // TODO(cl) - Using an array as the size function may not return the exact + // results and we should figure a way to avoid copying data anyway. + final List dps = new ArrayList(); + final boolean shift_is_int = (timeshift == Math.floor(timeshift)) && + !Double.isInfinite(timeshift); + final SeekableView view = points.iterator(); + while (view.hasNext()) { + DataPoint pt = view.next(); + if (shift_is_int) { + dps.add(MutableDataPoint.ofLongValue(pt.timestamp() + timeshift, + pt.longValue())); + } else { + // NaNs are fine here, they'll just be re-computed as NaN + dps.add(MutableDataPoint.ofDoubleValue(pt.timestamp() + timeshift, + timeshift * pt.toDouble())); + } + } + final DataPoint[] results = new DataPoint[dps.size()]; + dps.toArray(results); + return new PostAggregatedDataPoints(points, results); + } + + @Override + public String writeStringField(List params, String inner_expression) { + return "timeshift(" + inner_expression + ")"; + } +} From cdf1a116fb9df61cbb23344b1db9b8f0aaa76c05 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sun, 13 Mar 2016 15:43:17 -0700 Subject: [PATCH 138/233] Add the MetaDataCache plugin API that will let us bypass the built-in TSUID incrementing or "putting" calls to cut down on the amount of traffic sent to the server. Implementations can handle queuing and aggregation however they like. Signed-off-by: Chris Larsen --- Makefile.am | 1 + src/core/TSDB.java | 52 +++++++++++++++++++++----- src/meta/MetaDataCache.java | 73 +++++++++++++++++++++++++++++++++++++ src/utils/Config.java | 1 + 4 files changed, 118 insertions(+), 9 deletions(-) create mode 100644 src/meta/MetaDataCache.java diff --git a/Makefile.am b/Makefile.am index b569d7913b..db49f0589e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -70,6 +70,7 @@ tsdb_SRC := \ src/core/WritableDataPoints.java \ src/graph/Plot.java \ src/meta/Annotation.java \ + src/meta/MetaDataCache.java \ src/meta/TSMeta.java \ src/meta/TSUIDQuery.java \ src/meta/UIDMeta.java \ diff --git a/src/core/TSDB.java b/src/core/TSDB.java index 5e3f9657f7..72930603d1 100644 --- a/src/core/TSDB.java +++ b/src/core/TSDB.java @@ -52,6 +52,7 @@ import net.opentsdb.utils.PluginLoader; import net.opentsdb.utils.Threads; import net.opentsdb.meta.Annotation; +import net.opentsdb.meta.MetaDataCache; import net.opentsdb.meta.TSMeta; import net.opentsdb.meta.UIDMeta; import net.opentsdb.query.expression.ExpressionFactory; @@ -121,6 +122,9 @@ public final class TSDB { /** Optional real time pulblisher plugin to use if configured */ private RTPublisher rt_publisher = null; + /** Optional plugin for handling meta data caching and updating */ + private MetaDataCache meta_cache = null; + /** Plugin for dealing with data points that can't be stored */ private StorageExceptionHandler storage_exception_handler = null; @@ -313,6 +317,26 @@ public void initializePlugins(final boolean init_rpcs) { rt_publisher = null; } + // load the meta cache plugin if enabled + if (config.getBoolean("tsd.core.meta.cache.enable")) { + meta_cache = PluginLoader.loadSpecificPlugin( + config.getString("tsd.core.meta.cache.plugin"), MetaDataCache.class); + if (meta_cache == null) { + throw new IllegalArgumentException( + "Unable to locate meta cache plugin: " + + config.getString("tsd.core.meta.cache.plugin")); + } + try { + meta_cache.initialize(this); + } catch (Exception e) { + throw new RuntimeException( + "Failed to initialize meta cache plugin", e); + } + LOG.info("Successfully initialized meta cache plugin [" + + meta_cache.getClass().getCanonicalName() + "] version: " + + meta_cache.version()); + } + // load the storage exception plugin if enabled if (config.getBoolean("tsd.core.storage_exception_handler.enable")) { storage_exception_handler = PluginLoader.loadSpecificPlugin( @@ -827,15 +851,20 @@ private Deferred addPointInternal(final String metric, final byte[] tsuid = UniqueId.getTSUIDFromKey(row, METRICS_WIDTH, Const.TIMESTAMP_BYTES); - // for busy TSDs we may only enable TSUID tracking, storing a 1 in the - // counter field for a TSUID with the proper timestamp. If the user would - // rather have TSUID incrementing enabled, that will trump the PUT - if (config.enable_tsuid_tracking() && !config.enable_tsuid_incrementing()) { - final PutRequest tracking = new PutRequest(meta_table, tsuid, - TSMeta.FAMILY(), TSMeta.COUNTER_QUALIFIER(), Bytes.fromLong(1)); - client.put(tracking); - } else if (config.enable_tsuid_incrementing() || config.enable_realtime_ts()) { - TSMeta.incrementAndGetCounter(TSDB.this, tsuid); + // if the meta cache plugin is instantiated then tracking goes through it + if (meta_cache != null) { + meta_cache.increment(tsuid); + } else { + // for busy TSDs we may only enable TSUID tracking, storing a 1 in the + // counter field for a TSUID with the proper timestamp. If the user would + // rather have TSUID incrementing enabled, that will trump the PUT + if (config.enable_tsuid_tracking() && !config.enable_tsuid_incrementing()) { + final PutRequest tracking = new PutRequest(meta_table, tsuid, + TSMeta.FAMILY(), TSMeta.COUNTER_QUALIFIER(), Bytes.fromLong(1)); + client.put(tracking); + } else if (config.enable_tsuid_incrementing() || config.enable_realtime_ts()) { + TSMeta.incrementAndGetCounter(TSDB.this, tsuid); + } } if (rt_publisher != null) { @@ -974,6 +1003,11 @@ public Object call(ArrayList compactions) throws Exception { rt_publisher.getClass().getCanonicalName()); deferreds.add(rt_publisher.shutdown()); } + if (meta_cache != null) { + LOG.info("Shutting down meta cache plugin: " + + meta_cache.getClass().getCanonicalName()); + deferreds.add(meta_cache.shutdown()); + } if (storage_exception_handler != null) { LOG.info("Shutting down storage exception handler plugin: " + storage_exception_handler.getClass().getCanonicalName()); diff --git a/src/meta/MetaDataCache.java b/src/meta/MetaDataCache.java new file mode 100644 index 0000000000..96504b1ce7 --- /dev/null +++ b/src/meta/MetaDataCache.java @@ -0,0 +1,73 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2016 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.meta; + +import com.stumbleupon.async.Deferred; + +import net.opentsdb.core.TSDB; +import net.opentsdb.stats.StatsCollector; + +/** + * This is a first stab at a meta data cache. Initially it only handles + * incrementing TSUID counters in a local database. The class will then + * periodically sync the local counter cache with HBase and generate TSMeta + * objects if necessary. This keeps us from having to maintain thousands or + * millions of callback objects in memory while we wait for individual atomic + * increments per data point. + * @since 2.3 + */ +public abstract class MetaDataCache { + + /** + * Called by TSDB to initialize the plugin + * Implementations are responsible for setting up any IO they need as well + * as starting any required background threads. + * Note: Implementations should throw exceptions if they can't start + * up properly. The TSD will then shutdown so the operator can fix the + * problem. Please use IllegalArgumentException for configuration issues. + * @param tsdb The parent TSDB object + * @throws IllegalArgumentException if required configuration parameters are + * missing + * @throws Exception if something else goes wrong + */ + public abstract void initialize(final TSDB tsdb); + + /** + * Called when the TSD is shutting down to gracefully flush any buffers or + * close open connections. + */ + public abstract Deferred shutdown(); + + /** + * Should return the version of this plugin in the format: + * MAJOR.MINOR.MAINT, e.g. 2.0.1. The MAJOR version should match the major + * version of OpenTSDB the plugin is meant to work with. + * @return A version string used to log the loaded version + */ + public abstract String version(); + + /** + * Called by the TSD when a request for statistics collection has come in. The + * implementation may provide one or more statistics. If no statistics are + * available for the implementation, simply stub the method. + * @param collector The collector used for emitting statistics + */ + public abstract void collectStats(final StatsCollector collector); + + /** + * Increments the given TSUID in the cache by 1 + * @param tsuid The tsuid to increment + */ + public abstract void increment(final byte[] tsuid); + +} diff --git a/src/utils/Config.java b/src/utils/Config.java index 4e8cb9ed02..3191b1602a 100644 --- a/src/utils/Config.java +++ b/src/utils/Config.java @@ -491,6 +491,7 @@ protected void setDefaults() { default_map.put("tsd.core.meta.enable_realtime_uid", "false"); default_map.put("tsd.core.meta.enable_tsuid_incrementing", "false"); default_map.put("tsd.core.meta.enable_tsuid_tracking", "false"); + default_map.put("tsd.core.meta.cache.enable", "false"); default_map.put("tsd.core.plugin_path", ""); default_map.put("tsd.core.socket.timeout", "0"); default_map.put("tsd.core.tree.enable_processing", "false"); From d60a7ec40c6b9f6924a72d8cd9492b79b8367bb7 Mon Sep 17 00:00:00 2001 From: Jonathan Creasy Date: Wed, 2 Mar 2016 22:22:32 -0600 Subject: [PATCH 139/233] Attempting to build a framework for startup plugins. Testing with this [skeleton plugin](https://github.com/johann8384/opentsdb-discoveryplugins/blob/master/src/main/java/io/tsdb/opentsdb/discoveryplugins/CuratorPlugin.java) that I intend to turn into an [Apache Curator](https://curator.apache.org/curator-x-discovery/index.html) plugin. Fixes #716 --- Makefile.am | 1 + src/core/TSDB.java | 59 +++++++++++++++++++++------ src/tools/StartupPlugin.java | 78 ++++++++++++++++++++++++++++++++++++ src/tools/TSDMain.java | 64 ++++++++++++++++++++++++++++- src/utils/PluginLoader.java | 2 +- 5 files changed, 189 insertions(+), 15 deletions(-) create mode 100644 src/tools/StartupPlugin.java diff --git a/Makefile.am b/Makefile.am index db49f0589e..edaaf6e37a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -132,6 +132,7 @@ tsdb_SRC := \ src/tools/MetaPurge.java \ src/tools/MetaSync.java \ src/tools/Search.java \ + src/tools/StartupPlugin.java \ src/tools/TSDMain.java \ src/tools/TextImporter.java \ src/tools/TreeSync.java \ diff --git a/src/core/TSDB.java b/src/core/TSDB.java index 72930603d1..a0c6152007 100644 --- a/src/core/TSDB.java +++ b/src/core/TSDB.java @@ -59,6 +59,7 @@ import net.opentsdb.query.filter.TagVFilter; import net.opentsdb.search.SearchPlugin; import net.opentsdb.search.SearchQuery; +import net.opentsdb.tools.StartupPlugin; import net.opentsdb.stats.Histogram; import net.opentsdb.stats.QueryStats; import net.opentsdb.stats.StatsCollector; @@ -118,7 +119,10 @@ public final class TSDB { /** Search indexer to use if configure */ private SearchPlugin search = null; - + + /** Optional Startup Plugin to use if configured */ + private StartupPlugin startup = null; + /** Optional real time pulblisher plugin to use if configured */ private RTPublisher rt_publisher = null; @@ -233,7 +237,22 @@ public TSDB(final Config config) { public static byte[] FAMILY() { return FAMILY; } - + + /** + * Called by initializePlugins, also used to load startup plugins. + * @since 2.3 + */ + public static void loadPluginPath(final String plugin_path) throws RuntimeException { + if (plugin_path != null && !plugin_path.isEmpty()) { + try { + PluginLoader.loadJARs(plugin_path); + } catch (Exception e) { + throw new RuntimeException("Error loading plugins from plugin path: " + + plugin_path, e); + } + } + } + /** * Should be called immediately after construction to initialize plugins and * objects that rely on such. It also moves most of the potential exception @@ -246,16 +265,12 @@ public static byte[] FAMILY() { */ public void initializePlugins(final boolean init_rpcs) { final String plugin_path = config.getString("tsd.core.plugin_path"); - if (plugin_path != null && !plugin_path.isEmpty()) { - try { - PluginLoader.loadJARs(plugin_path); - } catch (Exception e) { - LOG.error("Error loading plugins from plugin path: " + plugin_path, e); - throw new RuntimeException("Error loading plugins from plugin path: " + - plugin_path, e); - } + try { + loadPluginPath(plugin_path); + } catch (Exception e) { + LOG.error("Error loading plugins from plugin path: " + plugin_path, e); } - + try { TagVFilter.initializeFilterMap(this); // @#$@%$%#$ing typed exceptions @@ -367,8 +382,21 @@ public void initializePlugins(final boolean init_rpcs) { public final HBaseClient getClient() { return this.client; } - - /** + + /** + * Sets the startup plugin so that it can be shutdown properly. + * @param startup + * @since 2.3 + */ + public final void setStartup(StartupPlugin startup) { this.startup = startup; } + /** + * Getter that returns the startup plugin object + * @return The StartupPlugin object + * @since 2.3 + */ + public final StartupPlugin getStartup() { return this.startup; } + + /** * Getter that returns the configuration object * @return The configuration object * @since 2.0 @@ -993,6 +1021,11 @@ public Object call(ArrayList compactions) throws Exception { LOG.info("Flushing compaction queue"); deferreds.add(compactionq.flush().addCallback(new CompactCB())); } + if (startup != null) { + LOG.info("Shutting down startup plugin: " + + startup.getClass().getCanonicalName()); + deferreds.add(startup.shutdown()); + } if (search != null) { LOG.info("Shutting down search plugin: " + search.getClass().getCanonicalName()); diff --git a/src/tools/StartupPlugin.java b/src/tools/StartupPlugin.java new file mode 100644 index 0000000000..580caa7ed3 --- /dev/null +++ b/src/tools/StartupPlugin.java @@ -0,0 +1,78 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2013 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.tools; + +import com.stumbleupon.async.Deferred; + +import net.opentsdb.utils.Config; +import net.opentsdb.stats.StatsCollector; + +/** + * The StartupPlugin allows users to interact with the OpenTSDB configuration + * as soon as it is completely parsed, just before OpenTSDB begins to use it. + *

+ * Note: Implementations must have a parameterless constructor. The + * {@link #initialize(TSDB)} method will be called immediately after the plugin is + * instantiated and before any other methods are called. + * @since 2.3 + */ +public abstract class StartupPlugin { + + /** + * Called by TSDB to initialize the plugin + * Implementations are responsible for setting up any IO they need as well + * as starting any required background threads. + * Note: Implementations should throw exceptions if they can't start + * up properly. The TSD will then shutdown so the operator can fix the + * problem. Please use IllegalArgumentException for configuration issues. + * @param tsdb The parent TSDB object + * @throws IllegalArgumentException if required configuration parameters are + * missing + * @throws Exception if something else goes wrong + */ + public abstract void initialize(final Config config) throws IllegalArgumentException, Exception; + + /** + * Called to gracefully shutdown the plugin. Implementations should close + * any IO they have open + * @return A deferred object that indicates the completion of the request. + * The {@link Object} has not special meaning and can be {@code null} + * (think of it as {@code Deferred}). + */ + public abstract Deferred shutdown(); + + /** + * Should return the version of this plugin in the format: + * MAJOR.MINOR.MAINT, e.g. "2.0.1". The MAJOR version should match the major + * version of OpenTSDB the plugin is meant to work with. + * @return A version string used to log the loaded version + */ + public abstract String version(); + + /** + * Should return the version of this plugin in the format: + * MAJOR.MINOR.MAINT, e.g. "2.0.1". The MAJOR version should match the major + * version of OpenTSDB the plugin is meant to work with. + * @return A version string used to log the loaded version + */ + public abstract String getType(); + + /** + * Called by the TSD when a request for statistics collection has come in. The + * implementation may provide one or more statistics. If no statistics are + * available for the implementation, simply stub the method. + * @param collector The collector used for emitting statistics + */ + public abstract void collectStats(final StatsCollector collector); + +} \ No newline at end of file diff --git a/src/tools/TSDMain.java b/src/tools/TSDMain.java index da02826a5f..ce23661335 100644 --- a/src/tools/TSDMain.java +++ b/src/tools/TSDMain.java @@ -13,10 +13,17 @@ package net.opentsdb.tools; import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.concurrent.Executor; import java.util.concurrent.Executors; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.jboss.netty.bootstrap.ServerBootstrap; import org.jboss.netty.channel.socket.ServerSocketChannelFactory; @@ -34,6 +41,8 @@ import net.opentsdb.tsd.RpcManager; import net.opentsdb.utils.Config; import net.opentsdb.utils.FileSystem; +import net.opentsdb.utils.Pair; +import net.opentsdb.utils.PluginLoader; import net.opentsdb.utils.Threads; /** @@ -53,6 +62,11 @@ static void usage(final ArgP argp, final String errmsg, final int retval) { System.exit(retval); } + /** A map of configured filters for use in querying */ + private static Map, Constructor>> + startupPlugin_filter_map = new HashMap, Constructor>>(); + private static final short DEFAULT_FLUSH_INTERVAL = 1000; private static TSDB tsdb = null; @@ -145,9 +159,21 @@ public static void main(String[] args) throws IOException { Executors.newCachedThreadPool(), Executors.newCachedThreadPool(), new Threads.PrependThreadNamer()); } - + + StartupPlugin startup = null; + try { + startup = loadStartupPlugins(config); + } catch (IllegalArgumentException e) { + usage(argp, e.getMessage(), 3); + } catch (Exception e) { + throw new RuntimeException("Initialization failed", e); + } + try { tsdb = new TSDB(config); + if (startup != null) { + tsdb.setStartup(startup); + } tsdb.initializePlugins(true); if (config.getBoolean("tsd.storage.hbase.prefetch_meta")) { tsdb.preFetchHBaseMeta(); @@ -198,6 +224,42 @@ public static void main(String[] args) throws IOException { // The server is now running in separate threads, we can exit main. } + private static StartupPlugin loadStartupPlugins(Config config) { + Logger log = LoggerFactory.getLogger(TSDMain.class); + + // load the startup plugin if enabled + StartupPlugin startup = null; + + if (config.getBoolean("tsd.startup.enable")) { + final String plugin_path = config.getString("tsd.core.plugin_path"); + + try { + TSDB.loadPluginPath(plugin_path); + } catch (Exception e) { + log.error("Error loading plugins from plugin path: " + plugin_path, e); + } + + startup = PluginLoader.loadSpecificPlugin( + config.getString("tsd.startup.plugin"), StartupPlugin.class); + if (startup == null) { + throw new IllegalArgumentException("Unable to locate startup plugin: " + + config.getString("tsd.startup.plugin")); + } + try { + startup.initialize(config); + } catch (Exception e) { + throw new RuntimeException("Failed to initialize startup plugin", e); + } + log.info("Successfully initialized startup plugin [" + + startup.getClass().getCanonicalName() + "] version: " + + startup.version()); + } else { + startup = null; + } + + return startup; + } + private static void registerShutdownHook() { final class TSDBShutdown extends Thread { public TSDBShutdown() { diff --git a/src/utils/PluginLoader.java b/src/utils/PluginLoader.java index 8dadfc5ec3..d66c75f0c9 100644 --- a/src/utils/PluginLoader.java +++ b/src/utils/PluginLoader.java @@ -104,7 +104,7 @@ public static T loadSpecificPlugin(final String name, while(it.hasNext()) { T plugin = it.next(); - if (plugin.getClass().getName().equals(name)) { + if (plugin.getClass().getName().equals(name) || plugin.getClass().getSuperclass().getName().equals(name)) { return plugin; } } From 5d53abbcdbfd12097918c1c9366be279a1bfc41b Mon Sep 17 00:00:00 2001 From: Jonathan Creasy Date: Wed, 9 Mar 2016 20:28:35 -0600 Subject: [PATCH 140/233] Fixed formatting From 5cdbf14dbbc50d0ebc06551f343305a447913261 Mon Sep 17 00:00:00 2001 From: Jonathan Creasy Date: Thu, 10 Mar 2016 12:53:31 -0600 Subject: [PATCH 141/233] Removed extra imports, fixed exception handling in initializePlugins --- src/core/TSDB.java | 17 +++++------------ src/tools/TSDMain.java | 6 +----- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/src/core/TSDB.java b/src/core/TSDB.java index a0c6152007..48440481a4 100644 --- a/src/core/TSDB.java +++ b/src/core/TSDB.java @@ -25,18 +25,11 @@ import com.stumbleupon.async.Deferred; import com.stumbleupon.async.DeferredGroupException; +import net.opentsdb.uid.NoSuchUniqueId; +import org.hbase.async.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.hbase.async.AppendRequest; -import org.hbase.async.Bytes; import org.hbase.async.Bytes.ByteMap; -import org.hbase.async.ClientStats; -import org.hbase.async.DeleteRequest; -import org.hbase.async.GetRequest; -import org.hbase.async.HBaseClient; -import org.hbase.async.HBaseException; -import org.hbase.async.KeyValue; -import org.hbase.async.PutRequest; import org.jboss.netty.util.HashedWheelTimer; import org.jboss.netty.util.Timeout; import org.jboss.netty.util.Timer; @@ -263,12 +256,12 @@ public static void loadPluginPath(final String plugin_path) throws RuntimeExcept * @throws IllegalArgumentException if a plugin could not be initialized * @since 2.0 */ - public void initializePlugins(final boolean init_rpcs) { + public void initializePlugins(final boolean init_rpcs) throws RuntimeException { final String plugin_path = config.getString("tsd.core.plugin_path"); try { loadPluginPath(plugin_path); - } catch (Exception e) { - LOG.error("Error loading plugins from plugin path: " + plugin_path, e); + } catch (RuntimeException e) { + throw e; } try { diff --git a/src/tools/TSDMain.java b/src/tools/TSDMain.java index ce23661335..08586aeb7f 100644 --- a/src/tools/TSDMain.java +++ b/src/tools/TSDMain.java @@ -14,15 +14,12 @@ import java.io.IOException; import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; + import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.HashMap; -import java.util.List; import java.util.Map; import org.jboss.netty.bootstrap.ServerBootstrap; @@ -34,7 +31,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import net.opentsdb.tools.BuildData; import net.opentsdb.core.TSDB; import net.opentsdb.core.Const; import net.opentsdb.tsd.PipelineFactory; From cb57dcc5cb72b9fc19f4cfa274d0defedf77fba2 Mon Sep 17 00:00:00 2001 From: Jonathan Creasy Date: Tue, 15 Mar 2016 13:25:18 -0500 Subject: [PATCH 142/233] Added read-only CLI flag --- src/tools/CliOptions.java | 2 ++ src/tools/TSDMain.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/tools/CliOptions.java b/src/tools/CliOptions.java index aeccb1bb36..e82c9825fa 100644 --- a/src/tools/CliOptions.java +++ b/src/tools/CliOptions.java @@ -140,6 +140,8 @@ static void overloadConfig(final ArgP argp, final Config config) { config.overrideConfig("tsd.core.flushinterval", entry.getValue()); } else if (entry.getKey().toLowerCase().equals("--backlog")) { config.overrideConfig("tsd.network.backlog", entry.getValue()); + } else if (entry.getKey().toLowerCase().equals("--read-only")) { + config.overrideConfig("tsd.mode", "ro"); } else if (entry.getKey().toLowerCase().equals("--bind")) { config.overrideConfig("tsd.network.bind", entry.getValue()); } else if (entry.getKey().toLowerCase().equals("--async-io")) { diff --git a/src/tools/TSDMain.java b/src/tools/TSDMain.java index da02826a5f..e1da594e5f 100644 --- a/src/tools/TSDMain.java +++ b/src/tools/TSDMain.java @@ -80,6 +80,8 @@ public static void main(String[] args) throws IOException { "Number for async io workers (default: cpu * 2)."); argp.addOption("--async-io", "true|false", "Use async NIO (default true) or traditional blocking io"); + argp.addOption("--read-only", "true|false", + "Set tsd.mode to ro (default false)"); argp.addOption("--backlog", "NUM", "Size of connection attempt queue (default: 3072 or kernel" + " somaxconn."); From 344d80871dccd8a6f7e7a5e0ed35beb261adeab3 Mon Sep 17 00:00:00 2001 From: Jonathan Creasy Date: Mon, 14 Mar 2016 15:34:36 -0500 Subject: [PATCH 143/233] Added ready function Fixes #716 Fixes #688 --- src/core/TSDB.java | 12 ++++++++++-- src/tools/StartupPlugin.java | 8 +++++++- src/tools/TSDMain.java | 13 ++++++++++--- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/core/TSDB.java b/src/core/TSDB.java index 48440481a4..3a7a7cedeb 100644 --- a/src/core/TSDB.java +++ b/src/core/TSDB.java @@ -25,11 +25,18 @@ import com.stumbleupon.async.Deferred; import com.stumbleupon.async.DeferredGroupException; -import net.opentsdb.uid.NoSuchUniqueId; -import org.hbase.async.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.hbase.async.AppendRequest; +import org.hbase.async.Bytes; import org.hbase.async.Bytes.ByteMap; +import org.hbase.async.ClientStats; +import org.hbase.async.DeleteRequest; +import org.hbase.async.GetRequest; +import org.hbase.async.HBaseClient; +import org.hbase.async.HBaseException; +import org.hbase.async.KeyValue; +import org.hbase.async.PutRequest; import org.jboss.netty.util.HashedWheelTimer; import org.jboss.netty.util.Timeout; import org.jboss.netty.util.Timer; @@ -37,6 +44,7 @@ import net.opentsdb.tree.TreeBuilder; import net.opentsdb.tsd.RTPublisher; import net.opentsdb.tsd.StorageExceptionHandler; +import net.opentsdb.uid.NoSuchUniqueId; import net.opentsdb.uid.NoSuchUniqueName; import net.opentsdb.uid.UniqueId; import net.opentsdb.uid.UniqueId.UniqueIdType; diff --git a/src/tools/StartupPlugin.java b/src/tools/StartupPlugin.java index 580caa7ed3..3c56464a68 100644 --- a/src/tools/StartupPlugin.java +++ b/src/tools/StartupPlugin.java @@ -15,6 +15,7 @@ import com.stumbleupon.async.Deferred; import net.opentsdb.utils.Config; +import net.opentsdb.core.TSDB; import net.opentsdb.stats.StatsCollector; /** @@ -40,7 +41,12 @@ public abstract class StartupPlugin { * missing * @throws Exception if something else goes wrong */ - public abstract void initialize(final Config config) throws IllegalArgumentException, Exception; + public abstract Config initialize(Config config) throws IllegalArgumentException, Exception; + + /** + * Called when the TSD is fully initialized and ready to handle traffic. + */ + public abstract void setReady(final TSDB tsdb) throws Exception; /** * Called to gracefully shutdown the plugin. Implementations should close diff --git a/src/tools/TSDMain.java b/src/tools/TSDMain.java index 08586aeb7f..c804c26adc 100644 --- a/src/tools/TSDMain.java +++ b/src/tools/TSDMain.java @@ -31,6 +31,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import net.opentsdb.tools.BuildData; import net.opentsdb.core.TSDB; import net.opentsdb.core.Const; import net.opentsdb.tsd.PipelineFactory; @@ -206,6 +207,9 @@ public static void main(String[] args) throws IOException { final InetSocketAddress addr = new InetSocketAddress(bindAddress, config.getInt("tsd.network.port")); server.bind(addr); + if (startup != null) { + startup.setReady(tsdb); + } log.info("Ready to serve on " + addr); } catch (Throwable e) { factory.releaseExternalResources(); @@ -226,17 +230,20 @@ private static StartupPlugin loadStartupPlugins(Config config) { // load the startup plugin if enabled StartupPlugin startup = null; - if (config.getBoolean("tsd.startup.enable")) { + if (config.getBoolean("tsd.startup.enabled")) { + log.debug("Startup Plugin is Enabled"); final String plugin_path = config.getString("tsd.core.plugin_path"); + final String plugin_class = config.getString("tsd.startup.plugin"); + log.debug("Plugin Path: " + plugin_path); try { TSDB.loadPluginPath(plugin_path); } catch (Exception e) { log.error("Error loading plugins from plugin path: " + plugin_path, e); } - startup = PluginLoader.loadSpecificPlugin( - config.getString("tsd.startup.plugin"), StartupPlugin.class); + log.debug("Attempt to Load: " + plugin_class); + startup = PluginLoader.loadSpecificPlugin(plugin_class, StartupPlugin.class); if (startup == null) { throw new IllegalArgumentException("Unable to locate startup plugin: " + config.getString("tsd.startup.plugin")); From 0f3a1476cda843c0579e2276d9bc3c2d0515acfe Mon Sep 17 00:00:00 2001 From: Nathan Owens Date: Fri, 11 Jul 2014 10:15:25 -0400 Subject: [PATCH 144/233] add datapoint counter --- src/core/TSDB.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/core/TSDB.java b/src/core/TSDB.java index 72930603d1..4389d40708 100644 --- a/src/core/TSDB.java +++ b/src/core/TSDB.java @@ -128,6 +128,9 @@ public final class TSDB { /** Plugin for dealing with data points that can't be stored */ private StorageExceptionHandler storage_exception_handler = null; + /** Datapoint Counter */ + private static final AtomicLong datapoints_received = new AtomicLong(); + /** * Constructor * @param client An initialized HBase client object @@ -722,6 +725,8 @@ public Deferred addPoint(final String metric, } else { v = Bytes.fromLong(value); } + collector.record("datapoints.received", datapoints_received, "type=all"); + datapoints_received.incrementAndGet(); final short flags = (short) (v.length - 1); // Just the length. return addPointInternal(metric, timestamp, v, tags, flags); } From 640ab2e357848f1fe19c26e5485b9a7cf280af8a Mon Sep 17 00:00:00 2001 From: Jonathan Creasy Date: Tue, 15 Mar 2016 15:29:51 -0500 Subject: [PATCH 145/233] Only record stat when collectStats() is called. Fixes #369 --- src/core/TSDB.java | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/core/TSDB.java b/src/core/TSDB.java index 4389d40708..500a1bbb64 100644 --- a/src/core/TSDB.java +++ b/src/core/TSDB.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; import com.stumbleupon.async.Callback; import com.stumbleupon.async.Deferred; @@ -127,9 +128,9 @@ public final class TSDB { /** Plugin for dealing with data points that can't be stored */ private StorageExceptionHandler storage_exception_handler = null; - - /** Datapoint Counter */ - private static final AtomicLong datapoints_received = new AtomicLong(); + + /** Datapoints Added */ + private static final AtomicLong datapoints_added = new AtomicLong(); /** * Constructor @@ -563,6 +564,13 @@ public void collectStats(final StatsCollector collector) { collector.clearExtraTag("class"); } + collector.addExtraTag("class", "TSDB"); + try { + collector.record("datapoints.added", datapoints_added, "type=all"); + } finally { + collector.clearExtraTag("class"); + } + collector.addExtraTag("class", "TsdbQuery"); try { collector.record("hbase.latency", TsdbQuery.scanlatency, "method=scan"); @@ -725,8 +733,7 @@ public Deferred addPoint(final String metric, } else { v = Bytes.fromLong(value); } - collector.record("datapoints.received", datapoints_received, "type=all"); - datapoints_received.incrementAndGet(); + final short flags = (short) (v.length - 1); // Just the length. return addPointInternal(metric, timestamp, v, tags, flags); } @@ -832,7 +839,7 @@ private Deferred addPointInternal(final String metric, Bytes.setInt(row, (int) base_time, metrics.width() + Const.SALT_WIDTH()); RowKey.prefixKeyWithSalt(row); - + Deferred result = null; if (config.enable_appends()) { final AppendDataPoints kv = new AppendDataPoints(qualifier, value); @@ -844,7 +851,11 @@ private Deferred addPointInternal(final String metric, final PutRequest point = new PutRequest(table, row, FAMILY, qualifier, value); result = client.put(point); } - + + // Count all added datapoints, not just those that came in through PUT rpc + // Will there be others? Well, something could call addPoint programatically right? + datapoints_added.incrementAndGet(); + // TODO(tsuna): Add a callback to time the latency of HBase and store the // timing in a moving Histogram (once we have a class for this). @@ -871,7 +882,7 @@ private Deferred addPointInternal(final String metric, TSMeta.incrementAndGetCounter(TSDB.this, tsuid); } } - + if (rt_publisher != null) { rt_publisher.sinkDataPoint(metric, timestamp, value, tags, tsuid, flags); } From 1236dff1c757b3847d3f8892d4bd8519f480fd84 Mon Sep 17 00:00:00 2001 From: Jonathan Creasy Date: Wed, 16 Mar 2016 00:18:12 -0500 Subject: [PATCH 146/233] Need to actually collect the stats from the Startup plugin --- src/core/TSDB.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/core/TSDB.java b/src/core/TSDB.java index 3a7a7cedeb..4e58e692ee 100644 --- a/src/core/TSDB.java +++ b/src/core/TSDB.java @@ -624,6 +624,14 @@ public void collectStats(final StatsCollector collector) { compactionq.collectStats(collector); // Collect Stats from Plugins + if (startup != null) { + try { + collector.addExtraTag("plugin", "startup"); + startup.collectStats(collector); + } finally { + collector.clearExtraTag("plugin"); + } + } if (rt_publisher != null) { try { collector.addExtraTag("plugin", "publish"); From 499e35c803be135dd3ae2f10dee506035d555f12 Mon Sep 17 00:00:00 2001 From: Jonathan Creasy Date: Wed, 16 Mar 2016 01:38:35 -0500 Subject: [PATCH 147/233] Implement default values for startup plugin --- src/utils/Config.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/utils/Config.java b/src/utils/Config.java index 3191b1602a..7655260bce 100644 --- a/src/utils/Config.java +++ b/src/utils/Config.java @@ -508,6 +508,8 @@ protected void setDefaults() { default_map.put("tsd.search.enable", "false"); default_map.put("tsd.search.plugin", ""); default_map.put("tsd.stats.canonical", "false"); + default_map.put("tsd.startup.enable", "false"); + default_map.put("tsd.startup.plugin", ""); default_map.put("tsd.storage.hbase.scanner.maxNumRows", "128"); default_map.put("tsd.storage.fix_duplicates", "false"); default_map.put("tsd.storage.flush_interval", "1000"); From 17132894e52a145abb2d157b2944a7a19ef438d7 Mon Sep 17 00:00:00 2001 From: Jonathan Creasy Date: Wed, 16 Mar 2016 01:40:03 -0500 Subject: [PATCH 148/233] Make sure value used matches config value --- src/tools/TSDMain.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/TSDMain.java b/src/tools/TSDMain.java index c804c26adc..b23008d46d 100644 --- a/src/tools/TSDMain.java +++ b/src/tools/TSDMain.java @@ -230,7 +230,7 @@ private static StartupPlugin loadStartupPlugins(Config config) { // load the startup plugin if enabled StartupPlugin startup = null; - if (config.getBoolean("tsd.startup.enabled")) { + if (config.getBoolean("tsd.startup.enable")) { log.debug("Startup Plugin is Enabled"); final String plugin_path = config.getString("tsd.core.plugin_path"); final String plugin_class = config.getString("tsd.startup.plugin"); From 6e19c1d54128ce1bdbe8e53bff4844a28ba642da Mon Sep 17 00:00:00 2001 From: Simon Matic Langford Date: Thu, 17 Mar 2016 22:02:01 +0000 Subject: [PATCH 149/233] Add checkbox for showing global annotations on ui --- src/tsd/client/QueryUi.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/tsd/client/QueryUi.java b/src/tsd/client/QueryUi.java index cf78c6feca..3c063745de 100644 --- a/src/tsd/client/QueryUi.java +++ b/src/tsd/client/QueryUi.java @@ -138,6 +138,7 @@ public class QueryUi implements EntryPoint, HistoryListener { private final ValidatedTextBox yformat = new ValidatedTextBox(); private final ValidatedTextBox y2format = new ValidatedTextBox(); private final ValidatedTextBox wxh = new ValidatedTextBox(); + private final CheckBox global_annotations = new CheckBox("Global annotations"); private String keypos = ""; // Position of the key on the graph. private final CheckBox horizontalkey = new CheckBox("Horizontal layout"); @@ -286,6 +287,8 @@ public void onValueChange(final ValueChangeEvent event) { y2format.addKeyPressHandler(refreshgraph); wxh.addBlurHandler(refreshgraph); wxh.addKeyPressHandler(refreshgraph); + global_annotations.addBlurHandler(refreshgraph); + global_annotations.addKeyPressHandler(refreshgraph); horizontalkey.addClickHandler(refreshgraph); keybox.addClickHandler(refreshgraph); nokey.addClickHandler(refreshgraph); @@ -378,6 +381,11 @@ public void onValueChange(final ValueChangeEvent event) { hbox.add(wxh); table.setWidget(0, 3, hbox); } + { + final HorizontalPanel hbox = new HorizontalPanel(); + hbox.add(global_annotations); + table.setWidget(0, 4, hbox); + } { addMetricForm("metric 1", 0); metrics.selectTab(0); @@ -404,6 +412,7 @@ public void onBeforeSelection(final BeforeSelectionEvent event) { optpanel.add(makeStylePanel(), "Style"); optpanel.selectTab(0); table.setWidget(1, 3, optpanel); + table.getFlexCellFormatter().setColSpan(1, 3, 2); final DecoratorPanel decorator = new DecoratorPanel(); decorator.setWidget(table); @@ -785,6 +794,7 @@ private void refreshFromQueryString() { maybeSetTextbox(qs, "start", start_datebox.getTextBox()); maybeSetTextbox(qs, "end", end_datebox.getTextBox()); setTextbox(qs, "wxh", wxh); + global_annotations.setValue(qs.containsKey("global_annotations")); autoreload.setValue(qs.containsKey("autoreload"), true); maybeSetTextbox(qs, "autoreload", autoreoload_interval); @@ -901,6 +911,9 @@ private void refreshGraph() { // a special parameter that the server will delete from the query. url.append("&ignore=" + nrequests++); } + if (global_annotations.getValue()) { + url.append("&global_annotations"); + } if(timezone.length() > 1) url.append("&tz=").append(timezone); From 6299ae0f1b468bcf6432e40301c09f86de046de6 Mon Sep 17 00:00:00 2001 From: Jonathan Creasy Date: Wed, 23 Mar 2016 11:36:33 -0500 Subject: [PATCH 150/233] Added check for malformed, double dot timestamp Fixes #724 --- src/utils/DateTime.java | 6 ++++-- test/utils/TestDateTime.java | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/utils/DateTime.java b/src/utils/DateTime.java index 6690573ddc..20d9d80711 100644 --- a/src/utils/DateTime.java +++ b/src/utils/DateTime.java @@ -124,8 +124,10 @@ public static final long parseDateTimeString(final String datetime, } else { try { long time; - if (datetime.contains(".")) { - if (datetime.charAt(10) != '.' || datetime.length() != 14) { + Boolean containsDot = datetime.contains("."); + Boolean containsTwoDots = datetime.matches(".*\\..*\\..*"); + if (containsDot) { + if (datetime.charAt(10) != '.' || datetime.length() != 14 || containsTwoDots) { throw new IllegalArgumentException("Invalid time: " + datetime + ". Millisecond timestamps must be in the format " + ". where the milliseconds are limited to 3 digits"); diff --git a/test/utils/TestDateTime.java b/test/utils/TestDateTime.java index 07facd6567..037bd53148 100644 --- a/test/utils/TestDateTime.java +++ b/test/utils/TestDateTime.java @@ -143,6 +143,11 @@ public void parseDateTimeStringUnixSecondsZero() { public void parseDateTimeStringUnixSecondsNegative() { DateTime.parseDateTimeString("-135596160", null); } + + @Test(expected = IllegalArgumentException.class) + public void parseDateTimeStringMultipleDots() { + DateTime.parseDateTimeString("1234567890.2.4", null); + } @Test public void parseDateTimeStringUnixSecondsInvalidLong() { From 20222f39ea9d29f079ffceb1068d6b8e4f235ce4 Mon Sep 17 00:00:00 2001 From: Jonathan Creasy Date: Wed, 23 Mar 2016 12:20:03 -0500 Subject: [PATCH 151/233] Disable STDOUT logging for packages Fixes #715 --- build-aux/deb/logback.xml | 2 +- build-aux/rpm/logback.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build-aux/deb/logback.xml b/build-aux/deb/logback.xml index 7ae2c3fcfc..9c32b2ecbe 100644 --- a/build-aux/deb/logback.xml +++ b/build-aux/deb/logback.xml @@ -67,7 +67,7 @@ - + diff --git a/build-aux/rpm/logback.xml b/build-aux/rpm/logback.xml index 7ae2c3fcfc..9c32b2ecbe 100644 --- a/build-aux/rpm/logback.xml +++ b/build-aux/rpm/logback.xml @@ -67,7 +67,7 @@ - + From 927b50d80d1730f5a7409838d6bbde1646c9d031 Mon Sep 17 00:00:00 2001 From: Jonathan Creasy Date: Wed, 23 Mar 2016 14:01:07 -0500 Subject: [PATCH 152/233] Updated regex to match dotted timestamp 1234567890.2.3 - no match 1234.56789.1234 - no match 1234567890.1234 - matches 1234.567890.2..3 - no match 1234567890.2..3 - no match Fixes #724 --- src/utils/DateTime.java | 12 +++++++++--- test/utils/TestDateTime.java | 30 ++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/utils/DateTime.java b/src/utils/DateTime.java index 20d9d80711..da2bcd2c68 100644 --- a/src/utils/DateTime.java +++ b/src/utils/DateTime.java @@ -125,9 +125,14 @@ public static final long parseDateTimeString(final String datetime, try { long time; Boolean containsDot = datetime.contains("."); - Boolean containsTwoDots = datetime.matches(".*\\..*\\..*"); + // [0-9]{10} ten digits + // \\. a dot + // [0-9]{1,3} one to three digits + Boolean isValidDottedMillesecond = datetime.matches("^[0-9]{10}\\.[0-9]{1,3}$"); + // one to ten digits (0-9) + Boolean isValidSeconds = datetime.matches("^[0-9]{1,10}$"); if (containsDot) { - if (datetime.charAt(10) != '.' || datetime.length() != 14 || containsTwoDots) { + if (!isValidDottedMillesecond) { throw new IllegalArgumentException("Invalid time: " + datetime + ". Millisecond timestamps must be in the format " + ". where the milliseconds are limited to 3 digits"); @@ -142,8 +147,9 @@ public static final long parseDateTimeString(final String datetime, } // this is a nasty hack to determine if the incoming request is // in seconds or milliseconds. This will work until November 2286 - if (datetime.length() <= 10) + if (datetime.length() <= 10) { time *= 1000; + } return time; } catch (NumberFormatException e) { throw new IllegalArgumentException("Invalid time: " + datetime diff --git a/test/utils/TestDateTime.java b/test/utils/TestDateTime.java index 037bd53148..1a8d1e41f8 100644 --- a/test/utils/TestDateTime.java +++ b/test/utils/TestDateTime.java @@ -144,10 +144,28 @@ public void parseDateTimeStringUnixSecondsNegative() { DateTime.parseDateTimeString("-135596160", null); } + /* + 1234567890.418 - match + 1234567890.1235 - no match + 1234567890.12 - matches + 1234.56789.003 - no match + 1234567890.3 - match + */ + @Test(expected = IllegalArgumentException.class) public void parseDateTimeStringMultipleDots() { DateTime.parseDateTimeString("1234567890.2.4", null); } + + @Test(expected = IllegalArgumentException.class) + public void parseDateTimeStringMultipleDotsEarlyDot() { + DateTime.parseDateTimeString("1234.56789.123", null); + } + + @Test(expected = IllegalArgumentException.class) + public void parseDateTimeStringEarlyandExtraDots() { + DateTime.parseDateTimeString("1234.56789.0.3", null); + } @Test public void parseDateTimeStringUnixSecondsInvalidLong() { @@ -167,6 +185,18 @@ public void parseDateTimeStringUnixMSDot() { long t = DateTime.parseDateTimeString("1355961603.418", null); assertEquals(1355961603418L, t); } + + @Test + public void parseDateTimeStringUnixMSDotShorter() { + long t = DateTime.parseDateTimeString("1355961603.41", null); + assertEquals(135596160341L, t); + } + + @Test + public void parseDateTimeStringUnixMSDotShortest() { + long t = DateTime.parseDateTimeString("1355961603.4", null); + assertEquals(13559616034L, t); + } @Test (expected = IllegalArgumentException.class) public void parseDateTimeStringUnixMSDotInvalid() { From 358a70e42c5b3d9371a55d60012a59878f121a1c Mon Sep 17 00:00:00 2001 From: Jonathan Creasy Date: Wed, 17 Feb 2016 15:49:18 -0600 Subject: [PATCH 153/233] Splicer needs these modifications --- src/core/AggregationIterator.java | 12 ++-- src/core/Aggregators.java | 101 +++++++++++++++++++++++++++ src/core/MutableDataPoint.java | 10 +++ src/query/expression/HighestMax.java | 2 +- 4 files changed, 118 insertions(+), 7 deletions(-) diff --git a/src/core/AggregationIterator.java b/src/core/AggregationIterator.java index 03bc93b8cf..4c4a917c3d 100644 --- a/src/core/AggregationIterator.java +++ b/src/core/AggregationIterator.java @@ -129,7 +129,7 @@ public class AggregationIterator implements SeekableView, DataPoint, * possibly store, provided that the most significant bit is reserved by * FLAG_FLOAT. */ - private static final long TIME_MASK = 0x7FFFFFFFFFFFFFFFL; + protected static final long TIME_MASK = 0x7FFFFFFFFFFFFFFFL; /** Aggregator to use to aggregate data points from different Spans. */ private final Aggregator aggregator; @@ -148,13 +148,13 @@ public class AggregationIterator implements SeekableView, DataPoint, * Once we reach the end of a Span, we'll null out its iterator from this * array. */ - private final SeekableView[] iterators; + protected final SeekableView[] iterators; /** Start time (UNIX timestamp in seconds or ms) on 32 bits ("unsigned" int). */ - private final long start_time; + protected final long start_time; /** End time (UNIX timestamp in seconds or ms) on 32 bits ("unsigned" int). */ - private final long end_time; + protected final long end_time; /** * The current and previous timestamps for the data points being used. @@ -178,7 +178,7 @@ public class AggregationIterator implements SeekableView, DataPoint, * linear interpolation anymore. * */ - private final long[] timestamps; // 32 bit unsigned + flag + protected final long[] timestamps; // 32 bit unsigned + flag /** * The current and next values for the data points being used. @@ -186,7 +186,7 @@ public class AggregationIterator implements SeekableView, DataPoint, * This array is also used to store floating point values, in which case * their binary representation just happens to be stored in a {@code long}. */ - private final long[] values; + protected final long[] values; /** The index in {@link #iterators} of the current Span being used. */ private int current; diff --git a/src/core/Aggregators.java b/src/core/Aggregators.java index 05eabf4e45..1d846301a5 100644 --- a/src/core/Aggregators.java +++ b/src/core/Aggregators.java @@ -15,6 +15,8 @@ import java.util.HashMap; import java.util.NoSuchElementException; import java.util.Set; +import java.util.Iterator; +import java.util.LinkedList; import org.apache.commons.math3.stat.descriptive.rank.Percentile; import org.apache.commons.math3.stat.descriptive.rank.Percentile.EstimationType; @@ -552,5 +554,104 @@ public double runDouble(final Doubles values) { } } + public static final class MovingAverage extends Aggregator { + private LinkedList list = new LinkedList(); + private final long numPoints; + private final boolean isTimeUnit; + public MovingAverage(final Interpolation method, final String name, long numPoints, boolean isTimeUnit) { + super(method, name); + this.numPoints = numPoints; + this.isTimeUnit = isTimeUnit; + } + + public long runLong(final Longs values) { + long sum = values.nextLongValue(); + while (values.hasNextValue()) { + sum += values.nextLongValue(); + } + + if (values instanceof DataPoint) { + long ts = ((DataPoint) values).timestamp(); + list.addFirst(new SumPoint(ts, sum)); + } + + long result = 0; + int count = 0; + + Iterator iter = list.iterator(); + SumPoint first = iter.next(); + boolean conditionMet = false; + + // now sum up the preceeding points + while (iter.hasNext()) { + SumPoint next = iter.next(); + result += (Long) next.val; + count++; + if (!isTimeUnit && count >= numPoints) { + conditionMet = true; + break; + } else if (isTimeUnit && ((first.ts - next.ts) > numPoints)) { + conditionMet = true; + break; + } + } + + if (!conditionMet || count == 0) { + return 0; + } + + return result / count; + } + + @Override + public double runDouble(Doubles values) { + double sum = values.nextDoubleValue(); + while (values.hasNextValue()) { + sum += values.nextDoubleValue(); + } + + if (values instanceof DataPoint) { + long ts = ((DataPoint) values).timestamp(); + list.addFirst(new SumPoint(ts, sum)); + } + + double result = 0; + int count = 0; + + Iterator iter = list.iterator(); + SumPoint first = iter.next(); + boolean conditionMet = false; + + // now sum up the preceeding points + while (iter.hasNext()) { + SumPoint next = iter.next(); + result += (Double) next.val; + count++; + if (!isTimeUnit && count >= numPoints) { + conditionMet = true; + break; + } else if (isTimeUnit && ((first.ts - next.ts) > numPoints)) { + conditionMet = true; + break; + } + } + + if (!conditionMet || count == 0) { + return 0; + } + + return result / count; + } + + class SumPoint { + long ts; + Object val; + + public SumPoint(long ts, Object val) { + this.ts = ts; + this.val = val; + } + } + } } diff --git a/src/core/MutableDataPoint.java b/src/core/MutableDataPoint.java index 2f51a4e3f9..3620a2da98 100644 --- a/src/core/MutableDataPoint.java +++ b/src/core/MutableDataPoint.java @@ -92,6 +92,16 @@ public static MutableDataPoint ofLongValue(final long timestamp, return dp; } + /** + * Copy constructor + * + * @param value A datapoint value. + */ + public static MutableDataPoint fromPoint(final DataPoint value) { + if (value.isInteger()) return ofLongValue(value.timestamp(), value.longValue()); + else return ofDoubleValue(value.timestamp(), value.doubleValue()); + } + @Override public long timestamp() { return timestamp; diff --git a/src/query/expression/HighestMax.java b/src/query/expression/HighestMax.java index 73029b23f4..0b942d5c1b 100644 --- a/src/query/expression/HighestMax.java +++ b/src/query/expression/HighestMax.java @@ -179,7 +179,7 @@ public String writeStringField(final List query_params, /** * Aggregator that stores the overall maximum value for the entire series */ - static class MaxCacheAggregator extends Aggregator { + public static class MaxCacheAggregator extends Aggregator { /** The total number of series in the result set, including sub queries and * group bys */ private final int total_series; From d46133405b98df52ccb1ad6c0176897c2728f67c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A7=AC=E5=B9=B3?= Date: Tue, 24 Nov 2015 19:43:06 +0800 Subject: [PATCH 154/233] tsd connections limit --- src/tools/TSDMain.java | 8 +++++++- src/tsd/ConnectionManager.java | 17 +++++++++++++++-- src/tsd/PipelineFactory.java | 8 ++++---- src/utils/Config.java | 1 + 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/tools/TSDMain.java b/src/tools/TSDMain.java index 4f1e3fd43e..37cbd7c3d2 100644 --- a/src/tools/TSDMain.java +++ b/src/tools/TSDMain.java @@ -138,6 +138,12 @@ public static void main(String[] args) throws IOException { } final ServerSocketChannelFactory factory; + int connectionsLimit = 0; + try { + connectionsLimit = config.getInt("tsd.connections.limit"); + } catch (NumberFormatException nfe) { + usage(argp, "Invalid connections limit", 1); + } if (config.getBoolean("tsd.network.async_io")) { int workers = Runtime.getRuntime().availableProcessors() * 2; if (config.hasProperty("tsd.network.worker_threads")) { @@ -188,7 +194,7 @@ public static void main(String[] args) throws IOException { // here to fail fast. final RpcManager manager = RpcManager.instance(tsdb); - server.setPipelineFactory(new PipelineFactory(tsdb, manager)); + server.setPipelineFactory(new PipelineFactory(tsdb, manager, connectionsLimit)); if (config.hasProperty("tsd.network.backlog")) { server.setOption("backlog", config.getInt("tsd.network.backlog")); } diff --git a/src/tsd/ConnectionManager.java b/src/tsd/ConnectionManager.java index 35c3288bad..b8b3ea2eb0 100644 --- a/src/tsd/ConnectionManager.java +++ b/src/tsd/ConnectionManager.java @@ -41,6 +41,10 @@ final class ConnectionManager extends SimpleChannelHandler { private static final AtomicLong exceptions_closed = new AtomicLong(); private static final AtomicLong exceptions_reset = new AtomicLong(); private static final AtomicLong exceptions_timeout = new AtomicLong(); + /** + * max connections can be serviced by tsd, if over limit, tsd will close new connection. + */ + private int connectionsLimit; private static final DefaultChannelGroup channels = new DefaultChannelGroup("all-channels"); @@ -50,7 +54,9 @@ static void closeAllConnections() { } /** Constructor. */ - public ConnectionManager() { + public ConnectionManager(int connectionsLimit) { + LOG.info("totalConnections limit is set : " + connectionsLimit); + this.connectionsLimit = connectionsLimit; } /** @@ -73,7 +79,14 @@ public static void collectStats(final StatsCollector collector) { @Override public void channelOpen(final ChannelHandlerContext ctx, - final ChannelStateEvent e) { + final ChannelStateEvent e) throws IOException { + if (connectionsLimit > 0) { + int channelsSize = channels.size(); + if (channelsSize >= connectionsLimit) { + e.getChannel().close(); + throw new IOException("now channels size " + channelsSize + " is exceed total connections limit " + connectionsLimit); + } + } channels.add(e.getChannel()); connections_established.incrementAndGet(); } diff --git a/src/tsd/PipelineFactory.java b/src/tsd/PipelineFactory.java index 85f66a7fbd..4037388fa8 100644 --- a/src/tsd/PipelineFactory.java +++ b/src/tsd/PipelineFactory.java @@ -47,7 +47,7 @@ public final class PipelineFactory implements ChannelPipelineFactory { // Those are sharable but maintain some state, so a single instance per // PipelineFactory is needed. - private final ConnectionManager connmgr = new ConnectionManager(); + private final ConnectionManager connmgr; private final DetectHttpOrRpc HTTP_OR_RPC = new DetectHttpOrRpc(); private final Timer timer; private final ChannelHandler timeoutHandler; @@ -70,7 +70,7 @@ public final class PipelineFactory implements ChannelPipelineFactory { * serializers */ public PipelineFactory(final TSDB tsdb) { - this(tsdb, RpcManager.instance(tsdb)); + this(tsdb, RpcManager.instance(tsdb), 0); } /** @@ -82,12 +82,13 @@ public PipelineFactory(final TSDB tsdb) { * @throws Exception if the HttpQuery handler is unable to load * serializers */ - public PipelineFactory(final TSDB tsdb, final RpcManager manager) { + public PipelineFactory(final TSDB tsdb, final RpcManager manager, final int connectionsLimit) { this.tsdb = tsdb; this.socketTimeout = tsdb.getConfig().getInt("tsd.core.socket.timeout"); timer = tsdb.getTimer(); this.timeoutHandler = new IdleStateHandler(timer, 0, 0, this.socketTimeout); this.rpchandler = new RpcHandler(tsdb, manager); + this.connmgr = new ConnectionManager(connectionsLimit); try { HttpQuery.initializeSerializerMaps(tsdb); } catch (RuntimeException e) { @@ -153,4 +154,3 @@ protected Object decode(final ChannelHandlerContext ctx, } } - \ No newline at end of file diff --git a/src/utils/Config.java b/src/utils/Config.java index 7655260bce..1b439c0f61 100644 --- a/src/utils/Config.java +++ b/src/utils/Config.java @@ -536,6 +536,7 @@ protected void setDefaults() { + "Content-Type, Accept, Origin, User-Agent, DNT, Cache-Control, " + "X-Mx-ReqToken, Keep-Alive, X-Requested-With, If-Modified-Since"); default_map.put("tsd.query.timeout", "0"); + default_map.put("tsd.connections.limit", "0"); for (Map.Entry entry : default_map.entrySet()) { if (!properties.containsKey(entry.getKey())) From 9d9e4757ce1b7c3cda739f4f7a36a1c9a3ab7a4f Mon Sep 17 00:00:00 2001 From: Jonathan Creasy Date: Wed, 23 Mar 2016 11:59:47 -0500 Subject: [PATCH 155/233] A few modifications to PR-638 Fixes #638 Added additional command line argument definition. --- src/tools/CliOptions.java | 2 ++ src/tools/TSDMain.java | 4 +++- src/tsd/ConnectionManager.java | 10 +++++++--- src/tsd/PipelineFactory.java | 2 +- src/utils/Config.java | 2 +- 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/tools/CliOptions.java b/src/tools/CliOptions.java index e82c9825fa..1ae0322701 100644 --- a/src/tools/CliOptions.java +++ b/src/tools/CliOptions.java @@ -148,6 +148,8 @@ static void overloadConfig(final ArgP argp, final Config config) { config.overrideConfig("tsd.network.async_io", entry.getValue()); } else if (entry.getKey().toLowerCase().equals("--worker-threads")) { config.overrideConfig("tsd.network.worker_threads", entry.getValue()); + } else if (entry.getKey().toLowerCase().equals("--max-connections")) { + config.overrideConfig("tsd.core.connections.limit", entry.getValue()); } } } diff --git a/src/tools/TSDMain.java b/src/tools/TSDMain.java index 37cbd7c3d2..eb61f4a6ac 100644 --- a/src/tools/TSDMain.java +++ b/src/tools/TSDMain.java @@ -96,6 +96,8 @@ public static void main(String[] args) throws IOException { argp.addOption("--backlog", "NUM", "Size of connection attempt queue (default: 3072 or kernel" + " somaxconn."); + argp.addOption("--max-connections", "NUM", + "Maximum number of connections to accept"); argp.addOption("--flush-interval", "MSEC", "Maximum time for which a new data point can be buffered" + " (default: " + DEFAULT_FLUSH_INTERVAL + ")."); @@ -140,7 +142,7 @@ public static void main(String[] args) throws IOException { final ServerSocketChannelFactory factory; int connectionsLimit = 0; try { - connectionsLimit = config.getInt("tsd.connections.limit"); + connectionsLimit = config.getInt("tsd.core.connections.limit"); } catch (NumberFormatException nfe) { usage(argp, "Invalid connections limit", 1); } diff --git a/src/tsd/ConnectionManager.java b/src/tsd/ConnectionManager.java index b8b3ea2eb0..f4d4523e3a 100644 --- a/src/tsd/ConnectionManager.java +++ b/src/tsd/ConnectionManager.java @@ -37,6 +37,7 @@ final class ConnectionManager extends SimpleChannelHandler { private static final Logger LOG = LoggerFactory.getLogger(ConnectionManager.class); private static final AtomicLong connections_established = new AtomicLong(); + private static final AtomicLong connections_rejected = new AtomicLong(); private static final AtomicLong exceptions_unknown = new AtomicLong(); private static final AtomicLong exceptions_closed = new AtomicLong(); private static final AtomicLong exceptions_reset = new AtomicLong(); @@ -65,6 +66,8 @@ public ConnectionManager(int connectionsLimit) { */ public static void collectStats(final StatsCollector collector) { collector.record("connectionmgr.connections", channels.size(), "type=open"); + collector.record("connectionmgr.connections", connections_rejected, + "type=rejected"); collector.record("connectionmgr.connections", connections_established, "type=total"); collector.record("connectionmgr.exceptions", exceptions_closed, @@ -81,10 +84,11 @@ public static void collectStats(final StatsCollector collector) { public void channelOpen(final ChannelHandlerContext ctx, final ChannelStateEvent e) throws IOException { if (connectionsLimit > 0) { - int channelsSize = channels.size(); - if (channelsSize >= connectionsLimit) { + int channelSize = channels.size(); + if (channelSize >= connectionsLimit) { e.getChannel().close(); - throw new IOException("now channels size " + channelsSize + " is exceed total connections limit " + connectionsLimit); + connections_rejected.incrementAndGet(); + throw new IOException("Channel size (" + channelSize + ") exceeds total connection limit (" + connectionsLimit + ")"); } } channels.add(e.getChannel()); diff --git a/src/tsd/PipelineFactory.java b/src/tsd/PipelineFactory.java index 4037388fa8..30389c34c6 100644 --- a/src/tsd/PipelineFactory.java +++ b/src/tsd/PipelineFactory.java @@ -70,7 +70,7 @@ public final class PipelineFactory implements ChannelPipelineFactory { * serializers */ public PipelineFactory(final TSDB tsdb) { - this(tsdb, RpcManager.instance(tsdb), 0); + this(tsdb, RpcManager.instance(tsdb), tsdb.getConfig().getInt("tsd.core.connections.limit")); } /** diff --git a/src/utils/Config.java b/src/utils/Config.java index 1b439c0f61..9f68736cb5 100644 --- a/src/utils/Config.java +++ b/src/utils/Config.java @@ -487,6 +487,7 @@ protected void setDefaults() { default_map.put("tsd.core.auto_create_metrics", "false"); default_map.put("tsd.core.auto_create_tagks", "true"); default_map.put("tsd.core.auto_create_tagvs", "true"); + default_map.put("tsd.core.connections.limit", "0"); default_map.put("tsd.core.meta.enable_realtime_ts", "false"); default_map.put("tsd.core.meta.enable_realtime_uid", "false"); default_map.put("tsd.core.meta.enable_tsuid_incrementing", "false"); @@ -536,7 +537,6 @@ protected void setDefaults() { + "Content-Type, Accept, Origin, User-Agent, DNT, Cache-Control, " + "X-Mx-ReqToken, Keep-Alive, X-Requested-With, If-Modified-Since"); default_map.put("tsd.query.timeout", "0"); - default_map.put("tsd.connections.limit", "0"); for (Map.Entry entry : default_map.entrySet()) { if (!properties.containsKey(entry.getKey())) From a807aa314b6973f3fc0197091971de9b9d60f240 Mon Sep 17 00:00:00 2001 From: Jonathan Creasy Date: Fri, 25 Mar 2016 20:18:58 -0500 Subject: [PATCH 156/233] Accept now as a time --- src/utils/DateTime.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/utils/DateTime.java b/src/utils/DateTime.java index da2bcd2c68..2d690e7e17 100644 --- a/src/utils/DateTime.java +++ b/src/utils/DateTime.java @@ -75,6 +75,9 @@ public static final long parseDateTimeString(final String datetime, final String tz) { if (datetime == null || datetime.isEmpty()) return -1; + if (datetime.toLowerCase().equals("now")) { + return System.currentTimeMillis(); + } if (datetime.toLowerCase().endsWith("-ago")) { long interval = DateTime.parseDuration( datetime.substring(0, datetime.length() - 4)); From d3fde219fcff4d062b90fb63f1afe03b3349eb0d Mon Sep 17 00:00:00 2001 From: Jonathan Creasy Date: Fri, 25 Mar 2016 20:49:09 -0500 Subject: [PATCH 157/233] Added test for Now timestamp Fixes #192 --- test/utils/TestDateTime.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/utils/TestDateTime.java b/test/utils/TestDateTime.java index 1a8d1e41f8..3f46f6b3c8 100644 --- a/test/utils/TestDateTime.java +++ b/test/utils/TestDateTime.java @@ -70,6 +70,12 @@ public void getTimezoneNull() { assertNull(DateTime.timezones.get("Nothere")); } + @Test + public void parseDateTimeStringNow() { + long t = DateTime.parseDateTimeString("now", null); + assertEquals(t, 1357300800000L); + } + @Test public void parseDateTimeStringRelativeS() { long t = DateTime.parseDateTimeString("60s-ago", null); From 7c0c5688a322421c106b0671f9a58f483eaabf96 Mon Sep 17 00:00:00 2001 From: Jonathan Creasy Date: Fri, 25 Mar 2016 21:08:49 -0500 Subject: [PATCH 158/233] Update NEWS --- NEWS | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 5378eb836c..c0950558e1 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,28 @@ OpenTSDB - User visible changes. +* Version 2.3.0 RC1 (2016-03-28) + +Noteworthy Changes: + - Introduced option --max-connection/tsd.core.connections.limit to set the maximum number + of connection a TSD will accept (#638) + - 'tsdb import' can now read from stdin (#580) + - Added datapoints counter (#369) + - Improved metadata storage performance (#699) + - added checkbox for showing global annotations in UI (#736) + - Added startup plugins, can be used for Service Discovery or other integration (#719) + - Added MetaDataCache plugin api + - Added timeshift() function (#175) + - Now align downsampling to Gregorian Calendar (#548, #657) + - Added support for latest Java versions + - Added NONE aggregator + - Added script to build OpenTSDB/HBase on OSX (#674) + - Added First/Last Downsampler + - Added query epxressions (alias(), scale(), absolute(), movingAverage(), highestCurrent(), + highestMax(), timeShift(), divide(), sum(), difference(), multiply()) (#625) +Bug Fixes: + - Some improperly formatted timestamps were allowed (#724) + - removed stdout logging from packaged logback.xml files (#715) + - * Version 2.2.0 (2016-02-14) Noteworthy Changes @@ -297,4 +320,4 @@ along with this library. If not, see . Local Variables: mode: outline -End: \ No newline at end of file +End: From 6071de1b47b38ec1b93c899a2b189a4459c73aee Mon Sep 17 00:00:00 2001 From: Jonathan Creasy Date: Fri, 25 Mar 2016 21:14:18 -0500 Subject: [PATCH 159/233] Added config value for max number of rows to be returned per Scanner round trip --- src/opentsdb.conf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/opentsdb.conf b/src/opentsdb.conf index 11d2a911df..cee3a4b402 100644 --- a/src/opentsdb.conf +++ b/src/opentsdb.conf @@ -46,6 +46,9 @@ tsd.http.cachedir = # default is 1,000 # tsd.storage.flush_interval = 1000 +# Max number of rows to be returned per Scanner round trip +# tsd.storage.hbase.scanner.maxNumRows = 128 + # Name of the HBase table where data points are stored, default is "tsdb" #tsd.storage.hbase.data_table = tsdb From 5747796c2a4398c43b332a165fbf39fb30f1d2a8 Mon Sep 17 00:00:00 2001 From: Jonathan Creasy Date: Tue, 17 Nov 2015 15:56:04 -0600 Subject: [PATCH 160/233] Apply regex based whitelists to new uid auto-creation --- src/opentsdb.conf | 16 ++++ src/tools/CliOptions.java | 11 +++ src/uid/UniqueId.java | 61 +++++++++++++++ src/utils/Config.java | 43 ++++++++++- test/uid/TestUniqueId.java | 150 +++++++++++++++++++++++++++++++++++-- 5 files changed, 272 insertions(+), 9 deletions(-) diff --git a/src/opentsdb.conf b/src/opentsdb.conf index cee3a4b402..7de2cb9e51 100644 --- a/src/opentsdb.conf +++ b/src/opentsdb.conf @@ -38,6 +38,22 @@ tsd.http.cachedir = # is False #tsd.core.auto_create_metrics = false +# Whether or no to evaluate new metric/tagk/tagv items against a whitelist, default +# is False +# tsd.core.auto_create_whitelist = false + +# Comma-Delimited list of regex patterns to match against new metric names, default +# is .*, examples might be ^awesome\..*$,^regexfoo[0-9].*$ +#tsd.core.auto_create_metrics_patterns = .* + +# Comma-Delimited list of regex patterns to match against new tagk names, default +# is .*, examples might be ^awesome\..*$,^regexfoo[0-9].*$ +#tsd.core.auto_create_tagk_patterns = .* + +# Comma-Delimited list of regex patterns to match against new tagv names, default +# is .*, examples might be ^awesome\..*$,^regexfoo[0-9].*$ +#tsd.core.auto_create_tagv_patterns = .* + # --------- STORAGE ---------- # Whether or not to enable data compaction in HBase, default is True #tsd.storage.enable_compaction = true diff --git a/src/tools/CliOptions.java b/src/tools/CliOptions.java index 1ae0322701..ec5eee6a4b 100644 --- a/src/tools/CliOptions.java +++ b/src/tools/CliOptions.java @@ -120,6 +120,17 @@ static void overloadConfig(final ArgP argp, final Config config) { // map the overrides if (entry.getKey().toLowerCase().equals("--auto-metric")) { config.overrideConfig("tsd.core.auto_create_metrics", "true"); + } else if (entry.getKey().toLowerCase().equals("--auto-metric-whitelist")) { + config.overrideConfig("tsd.core.auto_create_whitelist", "true"); + } else if (entry.getKey().toLowerCase().equals("--auto-metric-pattern")) { + config.overrideConfig("tsd.core.auto_create_metrics_patterns", + entry.getValue()); + } else if (entry.getKey().toLowerCase().equals("--auto-tagk-pattern")) { + config.overrideConfig("tsd.core.auto_create_tagk_patterns", + entry.getValue()); + } else if (entry.getKey().toLowerCase().equals("--auto-tagv-pattern")) { + config.overrideConfig("tsd.core.auto_create_tagv_patterns", + entry.getValue()); } else if (entry.getKey().toLowerCase().equals("--table")) { config.overrideConfig("tsd.storage.hbase.data_table", entry.getValue()); } else if (entry.getKey().toLowerCase().equals("--uidtable")) { diff --git a/src/uid/UniqueId.java b/src/uid/UniqueId.java index e94c988648..0acc58b836 100644 --- a/src/uid/UniqueId.java +++ b/src/uid/UniqueId.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; import javax.xml.bind.DatatypeConverter; @@ -53,6 +54,13 @@ */ @SuppressWarnings("deprecation") // Dunno why even with this, compiler warns. public final class UniqueId implements UniqueIdInterface { + /** Whether or not to check new UID against configured whitelists **/ + private Boolean useWhitelist = false; + /** Whitelists for various uid types **/ + private String auto_metric_patterns = ".*"; + private String auto_tagk_patterns = ".*"; + private String auto_tagv_patterns = ".*"; + private static final Logger LOG = LoggerFactory.getLogger(UniqueId.class); /** Enumerator for different types of UIDS @since 2.0 */ @@ -188,6 +196,14 @@ public short width() { /** @param tsdb Whether or not to track new UIDMeta objects */ public void setTSDB(final TSDB tsdb) { this.tsdb = tsdb; + try { + this.useWhitelist = tsdb.getConfig().auto_whitelist(); + this.auto_metric_patterns = tsdb.getConfig().auto_metric_patterns(); + this.auto_tagk_patterns = tsdb.getConfig().auto_tagk_patterns(); + this.auto_tagv_patterns = tsdb.getConfig().auto_tagv_patterns(); + } catch (Exception e) { + + } } /** The largest possible ID given the number of bytes the IDs are @@ -631,6 +647,10 @@ public byte[] getOrCreateId(final String name) throws HBaseException { try { return getIdAsync(name).joinUninterruptibly(); } catch (NoSuchUniqueName e) { + if (this.useWhitelist && !checkNameIsValid(name)) { + LOG.info("UID cannot be assigned, name is not acceptable because it fails to match the whitelist: " + name); + throw new RuntimeException("UID cannot be assigned, name is not acceptable because it fails to match the whitelist: " + name); + } Deferred assignment = null; boolean pending = false; synchronized (pending_assignments) { @@ -678,6 +698,47 @@ public byte[] getOrCreateId(final String name) throws HBaseException { } } + /** + * Checks to see if the provided string matches the acceptable + * patterns from the configuration. + *

+ * + * @param name The name to compare to the acceptable name regexes + * @return + */ + public Boolean checkNameIsValid(final String name) throws RuntimeException { + final List rxs = new ArrayList(); + try { + String uid_patterns; + switch (type) { + case METRIC: uid_patterns = this.auto_metric_patterns; + break; + case TAGK: uid_patterns = this.auto_tagk_patterns; + break; + case TAGV: uid_patterns = this.auto_tagv_patterns; + break; + default: + throw new RuntimeException("Should never be here"); + } + String[] patterns = uid_patterns.split(","); + + for (String pattern : patterns) { + rxs.add(Pattern.compile(pattern)); + } + + for (Pattern rx : rxs) { + if (rx.matcher(name).matches()) { + LOG.debug("Accepted name for UID: " + name + " based on '" + rx.toString() + "'"); + return true; + } + } + LOG.debug("Rejected name for UID: " + name); + return false; + } catch (Exception e) { + throw new RuntimeException("Failed to check name (" + name + ") against patterns.", e); + } + } + /** * Finds the ID associated with a given name or creates it. *

diff --git a/src/utils/Config.java b/src/utils/Config.java index 9f68736cb5..bced392630 100644 --- a/src/utils/Config.java +++ b/src/utils/Config.java @@ -69,7 +69,20 @@ public class Config { /** tsd.core.auto_create_tagv */ private boolean auto_tagv = true; - + + /** tsd.core.auto_create_whitelist */ + private boolean auto_whitelist = false; + + /** tsd.core.auto_create_metrics_patterns */ + private String auto_metric_patterns = ".*"; + + /** tsd.core.auto_create_tagk_patterns */ + private String auto_tagk_patterns = ".*"; + + /** tsd.core.auto_create_tagv_patterns */ + private String auto_tagv_patterns = ".*"; + + /** tsd.storage.enable_compaction */ private boolean enable_compactions = true; @@ -179,7 +192,25 @@ public boolean auto_tagk() { public boolean auto_tagv() { return auto_tagv; } - + + /** @return the auto_whitelist value */ + public boolean auto_whitelist() { return auto_whitelist; } + + /** @return the auto_metric value */ + public String auto_metric_patterns() { + return auto_metric_patterns; + } + + /** @return the auto_tagk value */ + public String auto_tagk_patterns() { + return auto_tagk_patterns; + } + + /** @return the auto_tagv value */ + public String auto_tagv_patterns() { + return auto_tagv_patterns; + } + /** @param auto_metric whether or not to auto create metrics */ public void setAutoMetric(boolean auto_metric) { this.auto_metric = auto_metric; @@ -487,6 +518,10 @@ protected void setDefaults() { default_map.put("tsd.core.auto_create_metrics", "false"); default_map.put("tsd.core.auto_create_tagks", "true"); default_map.put("tsd.core.auto_create_tagvs", "true"); + default_map.put("tsd.core.auto_create_whitelist", "false"); + default_map.put("tsd.core.auto_create_metrics_patterns", ".*"); + default_map.put("tsd.core.auto_create_tagk_patterns", ".*"); + default_map.put("tsd.core.auto_create_tagv_patterns", ".*"); default_map.put("tsd.core.connections.limit", "0"); default_map.put("tsd.core.meta.enable_realtime_ts", "false"); default_map.put("tsd.core.meta.enable_realtime_uid", "false"); @@ -634,6 +669,10 @@ protected void loadStaticVariables() { auto_metric = this.getBoolean("tsd.core.auto_create_metrics"); auto_tagk = this.getBoolean("tsd.core.auto_create_tagks"); auto_tagv = this.getBoolean("tsd.core.auto_create_tagvs"); + auto_whitelist = this.getBoolean("tsd.core.auto_create_whitelist"); + auto_metric_patterns = this.getString("tsdb.core.auto_create_metrics_patterns"); + auto_tagk_patterns = this.getString("tsdb.core.auto_create_tagk_patterns"); + auto_tagv_patterns = this.getString("tsdb.core.auto_create_tagv_patterns"); enable_compactions = this.getBoolean("tsd.storage.enable_compaction"); enable_appends = this.getBoolean("tsd.storage.enable_appends"); repair_appends = this.getBoolean("tsd.storage.repair_appends"); diff --git a/test/uid/TestUniqueId.java b/test/uid/TestUniqueId.java index a35aeeae3c..abc785f076 100644 --- a/test/uid/TestUniqueId.java +++ b/test/uid/TestUniqueId.java @@ -36,17 +36,12 @@ import org.junit.Test; import org.junit.runner.RunWith; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.fail; - import org.mockito.ArgumentMatcher; import org.mockito.InOrder; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; +import static org.junit.Assert.*; import static org.mockito.Matchers.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.argThat; @@ -271,6 +266,10 @@ public void getOrCreateIdAssignIdWithSuccess() { final byte[] id = { 0, 0, 5 }; final Config config = mock(Config.class); when(config.enable_realtime_uid()).thenReturn(false); + when(config.auto_whitelist()).thenReturn(false); + when(config.auto_metric_patterns()).thenReturn(".*"); + when(config.auto_tagk_patterns()).thenReturn(".*"); + when(config.auto_tagv_patterns()).thenReturn(".*"); final TSDB tsdb = mock(TSDB.class); when(tsdb.getConfig()).thenReturn(config); uid.setTSDB(tsdb); @@ -297,7 +296,130 @@ public void getOrCreateIdAssignIdWithSuccess() { // Reverse + forward mappings. verify(client, times(2)).compareAndSet(anyPut(), emptyArray()); } - + + @Test // Test the creation of an ID with no problem. + public void getOrCreateIdAssignWhitelistedIdWithSuccess() { + uid = new UniqueId(client, table, METRIC, 3); + final byte[] id = { 0, 0, 5 }; + final Config config = mock(Config.class); + when(config.enable_realtime_uid()).thenReturn(false); + when(config.auto_whitelist()).thenReturn(true); + when(config.auto_metric_patterns()).thenReturn(".*"); + when(config.auto_tagk_patterns()).thenReturn(".*"); + when(config.auto_tagv_patterns()).thenReturn(".*"); + final TSDB tsdb = mock(TSDB.class); + when(tsdb.getConfig()).thenReturn(config); + uid.setTSDB(tsdb); + + when(client.get(anyGet())) // null => ID doesn't exist. + .thenReturn(Deferred.>fromResult(null)); + // Watch this! ______,^ I'm writing C++ in Java! + + when(client.atomicIncrement(incrementForRow(MAXID))) + .thenReturn(Deferred.fromResult(5L)); + + when(client.compareAndSet(anyPut(), emptyArray())) + .thenReturn(Deferred.fromResult(true)) + .thenReturn(Deferred.fromResult(true)); + + assertArrayEquals(id, uid.getOrCreateId("foo")); + // Should be a cache hit since we created that entry. + assertArrayEquals(id, uid.getOrCreateId("foo")); + // Should be a cache hit too for the same reason. + assertEquals("foo", uid.getName(id)); + + verify(client).get(anyGet()); // Initial Get. + verify(client).atomicIncrement(incrementForRow(MAXID)); + // Reverse + forward mappings. + verify(client, times(2)).compareAndSet(anyPut(), emptyArray()); + } + + @Test(expected=RuntimeException.class) + public void getOrCreateIdAssignWhitelistedIdWithFailedWhitelist() { + uid = new UniqueId(client, table, METRIC, 3); + final byte[] id = { 0, 0, 5 }; + final Config config = mock(Config.class); + when(config.enable_realtime_uid()).thenReturn(false); + when(config.auto_whitelist()).thenReturn(true); + when(config.auto_metric_patterns()).thenReturn("^nomatch.*$"); + when(config.auto_tagk_patterns()).thenReturn("^sys\\.cpu\\.*$"); + when(config.auto_tagv_patterns()).thenReturn("^sys\\.cpu\\.*$"); + final TSDB tsdb = mock(TSDB.class); + when(tsdb.getConfig()).thenReturn(config); + uid.setTSDB(tsdb); + + when(client.get(anyGet())) // null => ID doesn't exist. + .thenReturn(Deferred.>fromResult(null)); + // Watch this! ______,^ I'm writing C++ in Java! + + when(client.atomicIncrement(incrementForRow(MAXID))) + .thenReturn(Deferred.fromResult(5L)); + + when(client.compareAndSet(anyPut(), emptyArray())) + .thenReturn(Deferred.fromResult(true)) + .thenReturn(Deferred.fromResult(true)); + + assertArrayEquals(id, uid.getOrCreateId("foo")); + // Should be a cache hit since we created that entry. + assertArrayEquals(id, uid.getOrCreateId("foo")); + // Should be a cache hit too for the same reason. + assertEquals("foo", uid.getName(id)); + + verify(client).get(anyGet()); // Initial Get. + verify(client).atomicIncrement(incrementForRow(MAXID)); + // Reverse + forward mappings. + verify(client, times(2)).compareAndSet(anyPut(), emptyArray()); + } + + @Test // Test the creation of an ID with no problem. + public void checkMetricAgainstWhitelist() { + setupWhitelists(METRIC); + assertTrue(uid.checkNameIsValid("sys.cpu.user")); + } + + @Test // Test the creation of an ID with no problem. + public void checkTagKAgainstWhitelist() { + setupWhitelists(TAGK); + assertTrue(uid.checkNameIsValid("sys.cpu.user")); + } + + @Test // Test the creation of an ID with no problem. + public void checkTagVAgainstWhitelist() { + setupWhitelists(TAGV); + assertTrue(uid.checkNameIsValid("sys.cpu.user")); + } + + @Test + public void checkMetricAgainstWhitelistFails() { + setupWhitelists(METRIC); + assertFalse(uid.checkNameIsValid("foo.badmetric")); + } + + @Test + public void checkTagKAgainstWhitelistFails() { + setupWhitelists(TAGK); + assertFalse(uid.checkNameIsValid("foo.badmetric")); + } + + @Test + public void checkTagVAgainstWhitelistFails() { + setupWhitelists(TAGV); + assertFalse(uid.checkNameIsValid("foo.badmetric")); + } + + private void setupWhitelists(String type) { + uid = new UniqueId(client, table, type, 3); + final Config config = mock(Config.class); + when(config.enable_realtime_uid()).thenReturn(false); + when(config.auto_whitelist()).thenReturn(true); + when(config.auto_metric_patterns()).thenReturn("sys.*"); + when(config.auto_tagk_patterns()).thenReturn("sys.*"); + when(config.auto_tagv_patterns()).thenReturn("sys.*"); + final TSDB tsdb = mock(TSDB.class); + when(tsdb.getConfig()).thenReturn(config); + uid.setTSDB(tsdb); + } + @Test // Test the creation of an ID when unable to increment MAXID public void getOrCreateIdUnableToIncrementMaxId() throws Exception { PowerMockito.mockStatic(Thread.class); @@ -428,6 +550,10 @@ public void getOrCreateIdWithICVFailure() { uid = new UniqueId(client, table, METRIC, 3); final Config config = mock(Config.class); when(config.enable_realtime_uid()).thenReturn(false); + when(config.auto_whitelist()).thenReturn(false); + when(config.auto_metric_patterns()).thenReturn(".*"); + when(config.auto_tagk_patterns()).thenReturn(".*"); + when(config.auto_tagv_patterns()).thenReturn(".*"); final TSDB tsdb = mock(TSDB.class); when(tsdb.getConfig()).thenReturn(config); uid.setTSDB(tsdb); @@ -460,6 +586,10 @@ public void getOrCreateIdPutsReverseMappingFirst() { uid = new UniqueId(client, table, METRIC, 3); final Config config = mock(Config.class); when(config.enable_realtime_uid()).thenReturn(false); + when(config.auto_whitelist()).thenReturn(false); + when(config.auto_metric_patterns()).thenReturn(".*"); + when(config.auto_tagk_patterns()).thenReturn(".*"); + when(config.auto_tagv_patterns()).thenReturn(".*"); final TSDB tsdb = mock(TSDB.class); when(tsdb.getConfig()).thenReturn(config); uid.setTSDB(tsdb); @@ -1180,6 +1310,12 @@ public void deleteNoSuchUniqueName() throws Exception { // ----------------- // private void setupStorage() throws Exception { + final Config config = mock(Config.class); + when(config.auto_whitelist()).thenReturn(false); + when(config.auto_metric_patterns()).thenReturn(".*"); + when(config.auto_tagk_patterns()).thenReturn(".*"); + when(config.auto_tagv_patterns()).thenReturn(".*"); + when(tsdb.getConfig()).thenReturn(config); when(tsdb.getClient()).thenReturn(client); storage = new MockBase(tsdb, client, true, true, true, true); From 4615b9b566e7dad355c870e90198da1c6b288235 Mon Sep 17 00:00:00 2001 From: Jonathan Creasy Date: Fri, 25 Mar 2016 23:59:10 -0500 Subject: [PATCH 161/233] removed extra try block --- src/uid/UniqueId.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/uid/UniqueId.java b/src/uid/UniqueId.java index 0acc58b836..8a1838c3bc 100644 --- a/src/uid/UniqueId.java +++ b/src/uid/UniqueId.java @@ -196,14 +196,10 @@ public short width() { /** @param tsdb Whether or not to track new UIDMeta objects */ public void setTSDB(final TSDB tsdb) { this.tsdb = tsdb; - try { - this.useWhitelist = tsdb.getConfig().auto_whitelist(); - this.auto_metric_patterns = tsdb.getConfig().auto_metric_patterns(); - this.auto_tagk_patterns = tsdb.getConfig().auto_tagk_patterns(); - this.auto_tagv_patterns = tsdb.getConfig().auto_tagv_patterns(); - } catch (Exception e) { - - } + this.useWhitelist = tsdb.getConfig().auto_whitelist(); + this.auto_metric_patterns = tsdb.getConfig().auto_metric_patterns(); + this.auto_tagk_patterns = tsdb.getConfig().auto_tagk_patterns(); + this.auto_tagv_patterns = tsdb.getConfig().auto_tagv_patterns(); } /** The largest possible ID given the number of bytes the IDs are From 176d6caef4fae7e89f53f222e4be31bb3a7fca76 Mon Sep 17 00:00:00 2001 From: Jonathan Creasy Date: Fri, 25 Mar 2016 21:29:18 -0500 Subject: [PATCH 162/233] Added support for using ms in a timestamp to explictely identify it as a ms timestamp Fixes #696 --- src/utils/DateTime.java | 6 ++++++ test/utils/TestDateTime.java | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/utils/DateTime.java b/src/utils/DateTime.java index 2d690e7e17..33f3ab3db5 100644 --- a/src/utils/DateTime.java +++ b/src/utils/DateTime.java @@ -75,9 +75,15 @@ public static final long parseDateTimeString(final String datetime, final String tz) { if (datetime == null || datetime.isEmpty()) return -1; + + if (datetime.matches("^[0-9]+ms$")) { + return Tags.parseLong(datetime.replaceFirst("^([0-9]+)(ms)$", "$1")); + } + if (datetime.toLowerCase().equals("now")) { return System.currentTimeMillis(); } + if (datetime.toLowerCase().endsWith("-ago")) { long interval = DateTime.parseDuration( datetime.substring(0, datetime.length() - 4)); diff --git a/test/utils/TestDateTime.java b/test/utils/TestDateTime.java index 3f46f6b3c8..b21bb25866 100644 --- a/test/utils/TestDateTime.java +++ b/test/utils/TestDateTime.java @@ -185,6 +185,18 @@ public void parseDateTimeStringUnixMS() { long t = DateTime.parseDateTimeString("1355961603418", null); assertEquals(1355961603418L, t); } + + @Test + public void parseDateTimeStringShortExplicitMS() { + long t = DateTime.parseDateTimeString("123123ms", null); + assertEquals(123123L, t); + } + + @Test + public void parseDateTimeStringExplicitMS() { + long t = DateTime.parseDateTimeString("1234567890123ms", null); + assertEquals(1234567890123L, t); + } @Test public void parseDateTimeStringUnixMSDot() { From 107d9e9d04abafef970579530415e54665e9c46b Mon Sep 17 00:00:00 2001 From: nickman Date: Sat, 26 Mar 2016 14:27:39 -0400 Subject: [PATCH 163/233] Impl for #743 Impl for #743 --- Makefile.am | 2 + src/stats/StatsCollector.java | 5 +++ src/tools/CliOptions.java | 4 +- src/tools/TSDMain.java | 2 + src/tools/TSDPort.java | 56 +++++++++++++++++++++++ src/utils/Config.java | 11 +++++ test/tsd/TestStatsWithPort.java | 79 +++++++++++++++++++++++++++++++++ 7 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 src/tools/TSDPort.java create mode 100644 test/tsd/TestStatsWithPort.java diff --git a/Makefile.am b/Makefile.am index edaaf6e37a..68a7a08b9e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -134,6 +134,7 @@ tsdb_SRC := \ src/tools/Search.java \ src/tools/StartupPlugin.java \ src/tools/TSDMain.java \ + src/tools/TSDPort.java \ src/tools/TextImporter.java \ src/tools/TreeSync.java \ src/tools/UidManager.java \ @@ -343,6 +344,7 @@ test_SRC := \ test/tsd/TestSuggestRpc.java \ test/tsd/TestTreeRpc.java \ test/tsd/TestUniqueIdRpc.java \ + test/tsd/TestStatsWithPort.java \ test/uid/TestNoSuchUniqueId.java \ test/uid/TestRandomUniqueId.java \ test/uid/TestUniqueId.java \ diff --git a/src/stats/StatsCollector.java b/src/stats/StatsCollector.java index 6d002e1568..4d9281f623 100644 --- a/src/stats/StatsCollector.java +++ b/src/stats/StatsCollector.java @@ -15,6 +15,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import net.opentsdb.tools.TSDPort; + import java.net.InetAddress; import java.net.UnknownHostException; import java.util.HashMap; @@ -50,6 +52,9 @@ public abstract class StatsCollector { */ public StatsCollector(final String prefix) { this.prefix = prefix; + if(TSDPort.isStatsWithPort()) { + addExtraTag("port", "" + TSDPort.getTSDPort()); + } } /** diff --git a/src/tools/CliOptions.java b/src/tools/CliOptions.java index ec5eee6a4b..8e9fc9b8ad 100644 --- a/src/tools/CliOptions.java +++ b/src/tools/CliOptions.java @@ -161,7 +161,9 @@ static void overloadConfig(final ArgP argp, final Config config) { config.overrideConfig("tsd.network.worker_threads", entry.getValue()); } else if (entry.getKey().toLowerCase().equals("--max-connections")) { config.overrideConfig("tsd.core.connections.limit", entry.getValue()); - } + } else if (entry.getKey().toLowerCase().equals("--statswport")) { + config.overrideConfig("tsd.core.stats_with_port", "true"); + } } } diff --git a/src/tools/TSDMain.java b/src/tools/TSDMain.java index eb61f4a6ac..4036ae3015 100644 --- a/src/tools/TSDMain.java +++ b/src/tools/TSDMain.java @@ -101,6 +101,7 @@ public static void main(String[] args) throws IOException { argp.addOption("--flush-interval", "MSEC", "Maximum time for which a new data point can be buffered" + " (default: " + DEFAULT_FLUSH_INTERVAL + ")."); + argp.addOption("--statswport", "Force all stats to include the port"); CliOptions.addAutoMetricFlag(argp); args = CliOptions.parse(argp, args); args = null; // free(). @@ -220,6 +221,7 @@ public static void main(String[] args) throws IOException { if (startup != null) { startup.setReady(tsdb); } + TSDPort.set(config); log.info("Ready to serve on " + addr); } catch (Throwable e) { factory.releaseExternalResources(); diff --git a/src/tools/TSDPort.java b/src/tools/TSDPort.java new file mode 100644 index 0000000000..861a1c0d2e --- /dev/null +++ b/src/tools/TSDPort.java @@ -0,0 +1,56 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2010-2012 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.tools; + +import net.opentsdb.utils.Config; + +/** + * Static reference to the TSD's listening port and stats port configuration + */ + +public class TSDPort { + /** The RPC listening port */ + private static int rpcPort = -1; + /** Indicates if RPC stats include the listening port. Set by config tsd.core.stats_with_port + or CLI option --statswport. */ + private static boolean statsWithPort = false; + + /** + * Sets the rpc port and stats config on TSD startup + * @param config The final config + */ + static void set(Config config) { + rpcPort = config.getInt("tsd.network.port"); + statsWithPort = config.getBoolean("tsd.core.stats_with_port"); + } + + /** + * Returns the TSD's listening port + * @return the port + */ + public static int getTSDPort() { + return rpcPort; + } + + /** + * Indicates if stats should be reported with the port as a tag + * @return true if stats should be reported with the port as a tag, false otherwise + */ + public static boolean isStatsWithPort() { + return statsWithPort; + } + + + private TSDPort() {} + +} diff --git a/src/utils/Config.java b/src/utils/Config.java index bced392630..5a61be5359 100644 --- a/src/utils/Config.java +++ b/src/utils/Config.java @@ -107,6 +107,10 @@ public class Config { /** tsd.http.request.enable_chunked */ private boolean enable_chunked_requests = false; + /** tsd.core.stats_with_port */ + private boolean stats_with_port = false; + + /** tsd.storage.fix_duplicates */ private boolean fix_duplicates = false; @@ -264,6 +268,11 @@ public boolean enable_chunked_requests() { return enable_chunked_requests; } + /** @return whether or not rpc stats should be broken out by port */ + public boolean rpc_stats_withport() { + return stats_with_port; + } + /** @return max incoming chunk size in bytes */ public int max_chunked_requests() { return max_chunked_requests; @@ -563,6 +572,7 @@ protected void setDefaults() { default_map.put("tsd.storage.compaction.min_flush_threshold", "100"); default_map.put("tsd.storage.compaction.max_concurrent_flushes", "10000"); default_map.put("tsd.storage.compaction.flush_speed", "2"); + default_map.put("tsd.core.stats_with_port", "false"); default_map.put("tsd.http.show_stack_trace", "true"); default_map.put("tsd.http.query.allow_delete", "false"); default_map.put("tsd.http.request.enable_chunked", "false"); @@ -689,6 +699,7 @@ protected void loadStaticVariables() { enable_tree_processing = this.getBoolean("tsd.core.tree.enable_processing"); fix_duplicates = this.getBoolean("tsd.storage.fix_duplicates"); scanner_max_num_rows = this.getInt("tsd.storage.hbase.scanner.maxNumRows"); + stats_with_port = this.getBoolean("tsd.core.stats_with_port"); } diff --git a/test/tsd/TestStatsWithPort.java b/test/tsd/TestStatsWithPort.java new file mode 100644 index 0000000000..85ca4001f1 --- /dev/null +++ b/test/tsd/TestStatsWithPort.java @@ -0,0 +1,79 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . + +package net.opentsdb.tsd; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +import org.junit.Assert; +import org.junit.Test; + +import net.opentsdb.stats.StatsCollector; +import net.opentsdb.tools.TSDPort; + +public class TestStatsWithPort { + + static final Pattern PORT_MATCH = Pattern.compile(" port="); + + @Test + public void testNoPort() { + setPortConfig(4242, false); + } + + @Test + public void testDefaultPort() { + setPortConfig(4242, true); + } + + + protected void doTest() { + final List lines = new ArrayList(); + StatsCollector sc = new StatsCollector("tsd") { + @Override + public final void emit(final String line) { + lines.add(line); + } + }; + sc.record("foo", -1); + + } + + protected void validateStats(final List lines) { + Pattern portMatch = Pattern.compile(" port=" + TSDPort.getTSDPort()); + for(String s: lines) { + if(!TSDPort.isStatsWithPort()) { + Assert.assertFalse("Stat had a port", PORT_MATCH.matcher(s).find()); + } else { + Assert.assertTrue("Stat did not have port", portMatch.matcher(s).find()); + } + } + } + + + public void setPortConfig(final Integer port, final Boolean statsWithPort) { + try { + Field portField = TSDPort.class.getDeclaredField("rpcPort"); + portField.setAccessible(true); + portField.set(null, port); + Field statsWPortField = TSDPort.class.getDeclaredField("statsWithPort"); + statsWPortField.setAccessible(true); + statsWPortField.set(null, statsWithPort); + } catch (Exception ex) { + throw new RuntimeException("Failed to set TCPPort fields", ex); + } + } + +} From ac1260e81e2214d9e18b7584c6be901de968b6b4 Mon Sep 17 00:00:00 2001 From: Can ZHANG Date: Wed, 3 Feb 2016 10:53:02 +0800 Subject: [PATCH 164/233] Remove extra getFromStorage --- src/meta/TSMeta.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/meta/TSMeta.java b/src/meta/TSMeta.java index 7c34cbfc2a..8e400b4536 100644 --- a/src/meta/TSMeta.java +++ b/src/meta/TSMeta.java @@ -338,15 +338,14 @@ public Deferred call(ArrayList validated) } /** - * Attempts to store a new, blank timeseries meta object via a CompareAndSet + * Attempts to store a new, blank timeseries meta object * Note: This should not be called by user accessible methods as it will * overwrite any data already in the column. * Note: This call does not guarantee that the UIDs exist before * storing as it should only be called *after* a data point has been recorded * or during a meta sync. * @param tsdb The TSDB to use for storage access - * @return True if the CAS completed successfully (and no TSMeta existed - * previously), false if something was already stored in the TSMeta column. + * @return True if the TSMeta created(or updated) successfully * @throws HBaseException if there was an issue fetching * @throws IllegalArgumentException if parsing failed * @throws JSONException if the object could not be serialized @@ -587,10 +586,10 @@ public Deferred call(Boolean success) throws Exception { } LOG.info("Successfullly created new TSUID entry for: " + meta); - final Deferred meta = getFromStorage(tsdb, tsuid) - .addCallbackDeferring( - new LoadUIDs(tsdb, UniqueId.uidToString(tsuid))); - return meta.addCallbackDeferring(new FetchNewCB()); + return Deferred.fromResult(meta) + .addCallbackDeferring( + new LoadUIDs(tsdb, UniqueId.uidToString(tsuid))) + .addCallbackDeferring(new FetchNewCB()); } } From 2892cf8fcb723502101a9d09ee57b031682d3b34 Mon Sep 17 00:00:00 2001 From: Can ZHANG Date: Thu, 4 Feb 2016 15:48:01 +0800 Subject: [PATCH 165/233] Create TSMeta by get then put If enable_tsuid_incrementing is false and config.enable_realtime_ts is true, TSMeta will be created through get, check and put, instead of atomicIncrement and put. Conflicts: src/core/TSDB.java --- src/core/TSDB.java | 4 ++- src/meta/TSMeta.java | 62 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/core/TSDB.java b/src/core/TSDB.java index 86270d891c..2726602a38 100644 --- a/src/core/TSDB.java +++ b/src/core/TSDB.java @@ -915,8 +915,10 @@ private Deferred addPointInternal(final String metric, final PutRequest tracking = new PutRequest(meta_table, tsuid, TSMeta.FAMILY(), TSMeta.COUNTER_QUALIFIER(), Bytes.fromLong(1)); client.put(tracking); - } else if (config.enable_tsuid_incrementing() || config.enable_realtime_ts()) { + } else if (config.enable_tsuid_incrementing() && config.enable_realtime_ts()) { TSMeta.incrementAndGetCounter(TSDB.this, tsuid); + } else if (!config.enable_tsuid_incrementing() && config.enable_realtime_ts()) { + TSMeta.storeIfNecessary(TSDB.this, tsuid); } } diff --git a/src/meta/TSMeta.java b/src/meta/TSMeta.java index 8e400b4536..47f70ee81d 100644 --- a/src/meta/TSMeta.java +++ b/src/meta/TSMeta.java @@ -611,6 +611,68 @@ public Deferred call(Boolean success) throws Exception { return tsdb.getClient().atomicIncrement(inc).addCallbackDeferring( new TSMetaCB()); } + + public static void storeIfNecessary(final TSDB tsdb, final byte[] tsuid) { + final GetRequest get = new GetRequest(tsdb.metaTable(), tsuid); + get.family(FAMILY); + get.qualifier(META_QUALIFIER); + + final class CreateNewCB implements Callback, Object> { + + @Override + public Deferred call(Object arg0) throws Exception { + final TSMeta meta = new TSMeta(tsuid, System.currentTimeMillis() / 1000); + + final class FetchNewCB implements Callback, TSMeta> { + + @Override + public Deferred call(TSMeta stored_meta) throws Exception { + + // pass to the search plugin + tsdb.indexTSMeta(stored_meta); + + // pass through the trees + tsdb.processTSMetaThroughTrees(stored_meta); + + return Deferred.fromResult(true); + } + } + + final class StoreNewCB implements Callback, Boolean> { + + @Override + public Deferred call(Boolean success) throws Exception { + if (!success) { + LOG.warn("Unable to save metadata: " + meta); + return Deferred.fromResult(false); + } + + LOG.info("Successfullly created new TSUID entry for: " + meta); + return Deferred.fromResult(meta) + .addCallbackDeferring( + new LoadUIDs(tsdb, UniqueId.uidToString(tsuid))) + .addCallbackDeferring(new FetchNewCB()); + } + } + + return meta.storeNew(tsdb).addCallbackDeferring(new StoreNewCB()); + } + } + + final class ExistsCB implements Callback, ArrayList> { + + @Override + public Deferred call(ArrayList row) throws Exception { + if (row == null || row.isEmpty() || row.get(0).value() == null) { + return Deferred.fromResult(new Object()) + .addCallbackDeferring(new CreateNewCB()); + } + return Deferred.fromResult(true); + } + } + + tsdb.getClient().get(get).addCallbackDeferring(new ExistsCB()); + } /** * Attempts to fetch the timeseries meta data from storage. From 5b5624a3e285253cb9bf7a6d092b7ae9057b93b8 Mon Sep 17 00:00:00 2001 From: Camden Narzt Date: Sat, 27 Feb 2016 09:48:23 -0700 Subject: [PATCH 166/233] Fix #707 mkdir_p is called from in a subdir of build so needs an extra `../` and there is no rule to create the `$(classes)` and everything builds without specifying them as dependencies of the jar. I've only tested these changes on the Makefile.in Conflicts: Makefile.am --- Makefile.am | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile.am b/Makefile.am index 68a7a08b9e..6f274a2373 100644 --- a/Makefile.am +++ b/Makefile.am @@ -565,7 +565,7 @@ install-data-local: staticroot install-data-lib install-data-tools \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ dstdir=`dirname "$(DESTDIR)$(staticdir)/$$p"`; \ if test -d "$$dstdir"; then :; else \ - echo " $(mkdir_p) '$$dstdir'"; $(mkdir_p) "$$dstdir"; fi; \ + echo " $(mkdir_p) '$$dstdir'"; ../$(mkdir_p) "$$dstdir"; fi; \ echo " $(INSTALL_DATA) '$$d$$p' '$(DESTDIR)$(staticdir)/$$p'"; \ $(INSTALL_DATA) "$$d$$p" "$(DESTDIR)$(staticdir)/$$p"; \ done @@ -718,7 +718,7 @@ manifest: .javac-stamp .git/HEAD echo "Implementation-Version: $(git_version)"; \ echo "Implementation-Vendor: $(spec_vendor)"; } >"$@" -$(jar): manifest .javac-stamp $(classes) +$(jar): manifest .javac-stamp $(JAR) cfm `basename $(jar)` manifest $(classes_with_nested_classes) $(get_expr_classes) \ || { rv=$$? && rm -f `basename $(jar)` && exit $$rv; } # ^^^^^^^^^^^^^^^^^^^^^^^ From b778e2b3cc9aee81a4bf0ab97a55f09c9fb79b7b Mon Sep 17 00:00:00 2001 From: Kieren Hynd Date: Tue, 29 Mar 2016 19:12:14 +0100 Subject: [PATCH 167/233] Correct some config option underscores in example opentsdb.conf --- src/opentsdb.conf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/opentsdb.conf b/src/opentsdb.conf index 7de2cb9e51..47c64c10a4 100644 --- a/src/opentsdb.conf +++ b/src/opentsdb.conf @@ -8,15 +8,15 @@ tsd.network.port = # Enables Nagel's algorithm to reduce the number of packets sent over the # network, default is True -#tsd.network.tcpnodelay = true +#tsd.network.tcp_no_delay = true # Determines whether or not to send keepalive packets to peers, default # is True -#tsd.network.keepalive = true +#tsd.network.keep_alive = true # Determines if the same socket should be used for new connections, default # is True -#tsd.network.reuseaddress = true +#tsd.network.reuse_address = true # Number of worker threads dedicated to Netty, defaults to # of CPUs * 2 #tsd.network.worker_threads = 8 From ace8ba96e6f0e7e9aae7b00ebe7e866b7d340ca9 Mon Sep 17 00:00:00 2001 From: dominosly Date: Tue, 29 Mar 2016 23:39:38 +0000 Subject: [PATCH 168/233] Fix in regex for parsing the java version to determine ALPN version in response to question on mailing list regarding big table builds failing on Debian (jesssie). Java version on jessie has suffix '-internal' added to version line found in 'java -version'. Regex in make file was testing for EOL ($). This just isn't necessary and will break on any version with a suffix. Works fine just matching for major, minor and sub version. Tested on Debian jessie and Ubuntu 14.04 LTS --- third_party/alpn-boot/include.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/alpn-boot/include.mk b/third_party/alpn-boot/include.mk index 13cd19dfa9..83ff6792bd 100644 --- a/third_party/alpn-boot/include.mk +++ b/third_party/alpn-boot/include.mk @@ -22,7 +22,7 @@ ALPN_BOOT_VERSION = $(shell version= ;\ echo "Failed to parse Java version";\ exit 1;\ fi; \ - if [[ $$version =~ ^([0-9]+\.[0-9]+)\.([0-9])[_Uu]([0-9]+)$$ ]]; then \ + if [[ $$version =~ ^([0-9]+\.[0-9]+)\.([0-9])[_Uu]([0-9]+) ]]; then \ major=$${BASH_REMATCH[1]};\ minor=$${BASH_REMATCH[2]}; \ sub=$${BASH_REMATCH[3]}; \ From 6a20a1db204debefa070e466be9eae48d4484366 Mon Sep 17 00:00:00 2001 From: Johannes Meixner Date: Tue, 5 Apr 2016 12:22:03 +0300 Subject: [PATCH 169/233] Use /usr/bin/env in shebang line - /usr/bin/python does not exist on FreeBSD - /usr/bin/env python will do the right thing on both Linux and BSD --- tools/check_tsd | 2 +- tools/opentsdb_restart.py | 2 +- tools/tsddrain.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/check_tsd b/tools/check_tsd index 101b5d5cec..237ec0e534 100755 --- a/tools/check_tsd +++ b/tools/check_tsd @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # # Script which queries TSDB with a given metric and alerts based on # supplied threshold. Compatible with Nagios output format, so can be diff --git a/tools/opentsdb_restart.py b/tools/opentsdb_restart.py index 31425750a8..9c63679c34 100644 --- a/tools/opentsdb_restart.py +++ b/tools/opentsdb_restart.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python """Restart opentsdb. Called using -XX:OnOutOfMemoryError= Because it's calling the 'service opentsdb' command, should be run as root. diff --git a/tools/tsddrain.py b/tools/tsddrain.py index 9a0fcf9b59..8bd595447c 100755 --- a/tools/tsddrain.py +++ b/tools/tsddrain.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # # This little script can be used to replace TSDs while performing prolonged # HBase or HDFS maintenances. It runs a simple, low-end TCP server to accept From e2d2078d822bd3c3c92ffcd06647840509aa796f Mon Sep 17 00:00:00 2001 From: Pradeep Chhetri Date: Thu, 21 May 2015 01:54:36 +0530 Subject: [PATCH 170/233] Server should just exit whenever there is some unrecognized option --- src/tools/CliOptions.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/CliOptions.java b/src/tools/CliOptions.java index 8e9fc9b8ad..2e55a7470d 100644 --- a/src/tools/CliOptions.java +++ b/src/tools/CliOptions.java @@ -77,7 +77,7 @@ static String[] parse(final ArgP argp, String[] args) { args = argp.parse(args); } catch (IllegalArgumentException e) { System.err.println("Invalid usage. " + e.getMessage()); - return null; + System.exit(2); } honorVerboseFlag(argp); return args; From c9fb82dfa6bbb508cd9e2b832ac72cff09163219 Mon Sep 17 00:00:00 2001 From: Vitaliy Fuks Date: Wed, 13 Apr 2016 14:59:45 -0400 Subject: [PATCH 171/233] Typo: tsd.network.keepalive is actually called tsd.network.keep_alive according to code and documentation. --- build-aux/deb/opentsdb.conf | 2 +- build-aux/rpm/opentsdb.conf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build-aux/deb/opentsdb.conf b/build-aux/deb/opentsdb.conf index f58d5cf14c..3d7db5bfa3 100644 --- a/build-aux/deb/opentsdb.conf +++ b/build-aux/deb/opentsdb.conf @@ -12,7 +12,7 @@ tsd.network.port = 4242 # Determines whether or not to send keepalive packets to peers, default # is True -#tsd.network.keepalive = true +#tsd.network.keep_alive = true # Determines if the same socket should be used for new connections, default # is True diff --git a/build-aux/rpm/opentsdb.conf b/build-aux/rpm/opentsdb.conf index a515418a7e..caf4599acc 100644 --- a/build-aux/rpm/opentsdb.conf +++ b/build-aux/rpm/opentsdb.conf @@ -12,7 +12,7 @@ tsd.network.port = 4242 # Determines whether or not to send keepalive packets to peers, default # is True -#tsd.network.keepalive = true +#tsd.network.keep_alive = true # Determines if the same socket should be used for new connections, default # is True From 0fd1cf53c245873f180151e3328803058d034dd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Str=C3=B6m?= Date: Sat, 16 Apr 2016 12:22:41 +0200 Subject: [PATCH 172/233] check_tsd: unbreak #760 Removes old dead code trying to use bad_percent property check_tsd: add support for new aggregators check_tsd: add support for -N for testing at a specific timestamp check_tsd: let verbose datapoints log indicate if value is ignored Also break early, relevant when running with -N mode --- tools/check_tsd | 49 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/tools/check_tsd b/tools/check_tsd index 237ec0e534..ecc42b0551 100755 --- a/tools/check_tsd +++ b/tools/check_tsd @@ -29,6 +29,13 @@ import sys import time from optparse import OptionParser +AGGREGATORS = ('avg', 'count', 'dev', + 'ep50r3', 'ep50r7', 'ep75r3', 'ep75r7', 'ep90r3', 'ep90r7', 'ep95r3', 'ep95r7', + 'ep99r3', 'ep99r7', 'ep999r3', 'ep999r7', + 'mimmin', 'mimmax', 'min', 'max', 'none', + 'p50', 'p75', 'p90', 'p95', 'p99', 'p999', + 'sum', 'zimsum') + def main(argv): """Pulls data out of the TSDB and do very simple alerting from Nagios.""" @@ -71,6 +78,9 @@ def main(argv): parser.add_option('-P', '--percent-over', dest='percent_over', default=0, metavar='PERCENT', type='float', help='Only alarm if PERCENT of the data' ' points violate the threshold.') + parser.add_option('-N', '--now', type='int', default=None, + metavar='UTC', + help='Set unix timestamp for "now", for testing') parser.add_option('-S', '--ssl', default=False, action='store_true', help='Make queries to OpenTSDB via SSL (https)') (options, args) = parser.parse_args(args=argv[1:]) @@ -78,9 +88,9 @@ def main(argv): # argument validation if options.comparator not in ('gt', 'ge', 'lt', 'le', 'eq', 'ne'): parser.error("Comparator '%s' not valid." % options.comparator) - elif options.downsample not in ('none', 'avg', 'min', 'sum', 'max'): + elif options.downsample not in ('none',)+AGGREGATORS: parser.error("Downsample '%s' not valid." % options.downsample) - elif options.aggregator not in ('avg', 'min', 'sum', 'max'): + elif options.aggregator not in AGGREGATORS: parser.error("Aggregator '%s' not valid." % options.aggregator) elif not options.metric: parser.error('You must specify a metric (option -m).') @@ -118,8 +128,16 @@ def main(argv): rate = 'rate:' else: rate = '' - url = ('/q?start=%ss-ago&m=%s:%s%s%s%s&ascii&nagios' - % (options.duration, options.aggregator, downsampling, rate, + + if options.now: + now = options.now + start = '%s' % (now - int(options.duration)) + else: + now = int(time.time()) + start = '%ss-ago' % options.duration + + url = ('/q?start=%s&m=%s:%s%s%s%s&ascii&nagios' + % (start, options.aggregator, downsampling, rate, options.metric, tags)) tsd = '%s:%d' % (options.host, options.port) if options.ssl: # Pick the class to instantiate first. @@ -139,7 +157,7 @@ def main(argv): peer = conn.sock.getpeername() print ('Connected to %s:%d' % (peer[0], peer[1])) conn.set_debuglevel(1) - now = int(time.time()) + try: conn.request('GET', url) res = conn.getresponse() @@ -159,8 +177,6 @@ def main(argv): return 2 # but we won! - if options.verbose: - print (datapoints) datapoints = datapoints.splitlines() def no_data_point(): @@ -182,12 +198,20 @@ def main(argv): nbad = 0 # How many bad values have we seen? ncrit = 0 # How many critical values have we seen? nwarn = 0 # How many warning values have we seen? - for datapoint in datapoints: - datapoint = datapoint.split() + for datapoint_str in datapoints: + datapoint = datapoint_str.split() ts = int(datapoint[1]) delta = now - ts if delta > options.duration or delta <= options.ignore_recent: + if options.verbose: + print "%s (ignored, delta %ds)" % (datapoint_str, delta) + if delta < 0: + break # Skip the rest, we got what we came for. continue # Ignore data points outside of our range. + + if options.verbose: + print datapoint_str + npoints += 1 val = datapoint[2] if '.' in val: @@ -228,13 +252,6 @@ def main(argv): bad_pct = nbad * 100.0 / npoints - if options.bad_percent is not None and rv > 0 \ - and bad_pct < options.bad_percent: - if options.verbose: - print 'ignoring alarm, less than %.1f%% bad values (found %.1f%%)' % \ - (options.bad_percent, bad_pct) - rv = 0 - # in nrpe, pipe character is something special, but it's used in tag # searches. Translate it to something else for the purposes of output. ttags = tags.replace("|",":") From 955fb3112d96cfab0179fc8a06f383f9c32d3393 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Mon, 18 Apr 2016 16:54:58 -0700 Subject: [PATCH 173/233] Add comments and UTs to 931242b5db4f8ab79c0346d876c53cc96349ba3d as well as clean up the deferreds a bit. Signed-off-by: Chris Larsen --- src/meta/TSMeta.java | 24 +++++++++++--------- test/meta/TestTSMeta.java | 46 ++++++++++++++++++++++++++------------- 2 files changed, 45 insertions(+), 25 deletions(-) diff --git a/src/meta/TSMeta.java b/src/meta/TSMeta.java index 47f70ee81d..e1c72f280c 100644 --- a/src/meta/TSMeta.java +++ b/src/meta/TSMeta.java @@ -586,9 +586,7 @@ public Deferred call(Boolean success) throws Exception { } LOG.info("Successfullly created new TSUID entry for: " + meta); - return Deferred.fromResult(meta) - .addCallbackDeferring( - new LoadUIDs(tsdb, UniqueId.uidToString(tsuid))) + return new LoadUIDs(tsdb, UniqueId.uidToString(tsuid)).call(meta) .addCallbackDeferring(new FetchNewCB()); } @@ -612,7 +610,16 @@ public Deferred call(Boolean success) throws Exception { new TSMetaCB()); } - public static void storeIfNecessary(final TSDB tsdb, final byte[] tsuid) { + /** + * Attempts to fetch the meta column and if null, attempts to write a new + * column using {@link #storeNew}. + * @param tsdb The TSDB instance to use for access. + * @param tsuid The TSUID of the time series. + * @return A deferred with a true if the meta exists or was created, false + * if the meta did not exist and writing failed. + */ + public static Deferred storeIfNecessary(final TSDB tsdb, + final byte[] tsuid) { final GetRequest get = new GetRequest(tsdb.metaTable(), tsuid); get.family(FAMILY); get.qualifier(META_QUALIFIER); @@ -648,9 +655,7 @@ public Deferred call(Boolean success) throws Exception { } LOG.info("Successfullly created new TSUID entry for: " + meta); - return Deferred.fromResult(meta) - .addCallbackDeferring( - new LoadUIDs(tsdb, UniqueId.uidToString(tsuid))) + return new LoadUIDs(tsdb, UniqueId.uidToString(tsuid)).call(meta) .addCallbackDeferring(new FetchNewCB()); } } @@ -664,14 +669,13 @@ final class ExistsCB implements Callback, ArrayList> @Override public Deferred call(ArrayList row) throws Exception { if (row == null || row.isEmpty() || row.get(0).value() == null) { - return Deferred.fromResult(new Object()) - .addCallbackDeferring(new CreateNewCB()); + return new CreateNewCB().call(null); } return Deferred.fromResult(true); } } - tsdb.getClient().get(get).addCallbackDeferring(new ExistsCB()); + return tsdb.getClient().get(get).addCallbackDeferring(new ExistsCB()); } /** diff --git a/test/meta/TestTSMeta.java b/test/meta/TestTSMeta.java index 49c6e2f390..3984495aee 100644 --- a/test/meta/TestTSMeta.java +++ b/test/meta/TestTSMeta.java @@ -62,6 +62,7 @@ public final class TestTSMeta { private final static byte[] NAME_FAMILY = "name".getBytes(MockBase.ASCII()); private final static byte[] META_TABLE = "tsdb-meta".getBytes(MockBase.ASCII()); private final static byte[] UID_TABLE = "tsdb-uid".getBytes(MockBase.ASCII()); + private final static byte[] TSUID = new byte[] { 0, 0, 1, 0, 0, 1, 0, 0, 1 }; private TSDB tsdb; private Config config; private HBaseClient client = mock(HBaseClient.class); @@ -120,7 +121,7 @@ public void before() throws Exception { "1328140801,\"displayName\":\"Web server 1\"}") .getBytes(MockBase.ASCII())); - storage.addColumn(META_TABLE, new byte[] { 0, 0, 1, 0, 0, 1, 0, 0, 1 }, + storage.addColumn(META_TABLE, TSUID, NAME_FAMILY, "ts_meta".getBytes(MockBase.ASCII()), ("{\"tsuid\":\"000001000001000001\",\"" + @@ -128,7 +129,7 @@ public void before() throws Exception { "\"custom\":null,\"units\":\"\",\"retention\":42,\"max\":1.0,\"min\":" + "\"NaN\",\"displayName\":\"Display\",\"dataType\":\"Data\"}") .getBytes(MockBase.ASCII())); - storage.addColumn(META_TABLE, new byte[] { 0, 0, 1, 0, 0, 1, 0, 0, 1 }, + storage.addColumn(META_TABLE, TSUID, TSMeta.FAMILY, "ts_ctr".getBytes(MockBase.ASCII()), Bytes.fromLong(1L)); @@ -260,7 +261,7 @@ public void deleteNull() throws Exception { @Test public void syncToStorage() throws Exception { - meta = new TSMeta(new byte[] { 0, 0, 1, 0, 0, 1, 0, 0, 1 }, 1357300800000L); + meta = new TSMeta(TSUID, 1357300800000L); meta.setDisplayName("New DN"); meta.syncToStorage(tsdb, false).joinUninterruptibly(); assertEquals("New DN", meta.getDisplayName()); @@ -269,7 +270,7 @@ public void syncToStorage() throws Exception { @Test public void syncToStorageOverwrite() throws Exception { - meta = new TSMeta(new byte[] { 0, 0, 1, 0, 0, 1, 0, 0, 1 }, 1357300800000L); + meta = new TSMeta(TSUID, 1357300800000L); meta.setDisplayName("New DN"); meta.syncToStorage(tsdb, true).joinUninterruptibly(); assertEquals("New DN", meta.getDisplayName()); @@ -290,14 +291,14 @@ public void syncToStorageNullTSUID() throws Exception { @Test (expected = IllegalArgumentException.class) public void syncToStorageDoesNotExist() throws Exception { - storage.flushRow(META_TABLE, new byte[] { 0, 0, 1, 0, 0, 1, 0, 0, 1 }); - meta = new TSMeta(new byte[] { 0, 0, 1, 0, 0, 1, 0, 0, 1 }, 1357300800000L); + storage.flushRow(META_TABLE, TSUID); + meta = new TSMeta(TSUID, 1357300800000L); meta.syncToStorage(tsdb, false).joinUninterruptibly(); } @Test public void storeNew() throws Exception { - meta = new TSMeta(new byte[] { 0, 0, 1, 0, 0, 1, 0, 0, 1 }, 1357300800000L); + meta = new TSMeta(TSUID, 1357300800000L); meta.setDisplayName("New DN"); meta.storeNew(tsdb); assertEquals("New DN", meta.getDisplayName()); @@ -323,7 +324,7 @@ public void metaExistsInStorage() throws Exception { @Test public void metaExistsInStorageNot() throws Exception { - storage.flushRow(META_TABLE, new byte[] { 0, 0, 1, 0, 0, 1, 0, 0, 1 }); + storage.flushRow(META_TABLE, TSUID); assertFalse(TSMeta.metaExistsInStorage(tsdb, "000001000001000001") .joinUninterruptibly()); } @@ -331,14 +332,14 @@ public void metaExistsInStorageNot() throws Exception { @Test public void counterExistsInStorage() throws Exception { assertTrue(TSMeta.counterExistsInStorage(tsdb, - new byte[] { 0, 0, 1, 0, 0, 1, 0, 0, 1 }).joinUninterruptibly()); + TSUID).joinUninterruptibly()); } @Test public void counterExistsInStorageNot() throws Exception { - storage.flushRow(META_TABLE, new byte[] { 0, 0, 1, 0, 0, 1, 0, 0, 1 }); + storage.flushRow(META_TABLE, TSUID); assertFalse(TSMeta.counterExistsInStorage(tsdb, - new byte[] { 0, 0, 1, 0, 0, 1, 0, 0, 1 }).joinUninterruptibly()); + TSUID).joinUninterruptibly()); } @Test @@ -378,12 +379,27 @@ public void COUNTER_QUALIFIER() throws Exception { TSMeta.COUNTER_QUALIFIER()); } + @Test + public void storeIfNecessaryExists() throws Exception { + assertTrue(TSMeta.storeIfNecessary(tsdb, TSUID).join()); + } + + @Test + public void storeIfNecessaryMissing() throws Exception { + storage.flushRow(META_TABLE, TSUID); + assertNull(storage.getColumn(META_TABLE, TSUID, NAME_FAMILY, + TSMeta.META_QUALIFIER())); + assertTrue(TSMeta.storeIfNecessary(tsdb, TSUID).join()); + assertNotNull(storage.getColumn(META_TABLE, TSUID, NAME_FAMILY, + TSMeta.META_QUALIFIER())); + } + @Test public void parseFromColumn() throws Exception { final KeyValue column = mock(KeyValue.class); - when(column.key()).thenReturn(new byte[] { 0, 0, 1, 0, 0, 1, 0, 0, 1 }); + when(column.key()).thenReturn(TSUID); when(column.value()).thenReturn(storage.getColumn(META_TABLE, - new byte[] { 0, 0, 1, 0, 0, 1, 0, 0, 1 }, + TSUID, NAME_FAMILY, "ts_meta".getBytes(MockBase.ASCII()))); final TSMeta meta = TSMeta.parseFromColumn(tsdb, column, false) @@ -396,9 +412,9 @@ public void parseFromColumn() throws Exception { @Test public void parseFromColumnWithUIDMeta() throws Exception { final KeyValue column = mock(KeyValue.class); - when(column.key()).thenReturn(new byte[] { 0, 0, 1, 0, 0, 1, 0, 0, 1 }); + when(column.key()).thenReturn(TSUID); when(column.value()).thenReturn(storage.getColumn(META_TABLE, - new byte[] { 0, 0, 1, 0, 0, 1, 0, 0, 1 }, + TSUID, NAME_FAMILY, "ts_meta".getBytes(MockBase.ASCII()))); final TSMeta meta = TSMeta.parseFromColumn(tsdb, column, true) From a2f4c72ae65fa38e426b1d032e2f2602ea41eab0 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Mon, 18 Apr 2016 17:20:28 -0700 Subject: [PATCH 174/233] Fix TSDB write data around the TSMeta and TSUID incrementation. Also update news. Signed-off-by: Chris Larsen --- src/core/TSDB.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/core/TSDB.java b/src/core/TSDB.java index d35c7d2a15..2842b89067 100644 --- a/src/core/TSDB.java +++ b/src/core/TSDB.java @@ -908,17 +908,18 @@ private Deferred addPointInternal(final String metric, if (meta_cache != null) { meta_cache.increment(tsuid); } else { - // for busy TSDs we may only enable TSUID tracking, storing a 1 in the - // counter field for a TSUID with the proper timestamp. If the user would - // rather have TSUID incrementing enabled, that will trump the PUT - if (config.enable_tsuid_tracking() && !config.enable_tsuid_incrementing()) { + if (config.enable_tsuid_tracking()) { + if (config.enable_realtime_ts()) { + if (config.enable_tsuid_incrementing()) { + TSMeta.incrementAndGetCounter(TSDB.this, tsuid); + } else { + TSMeta.storeIfNecessary(TSDB.this, tsuid); + } + } else { final PutRequest tracking = new PutRequest(meta_table, tsuid, TSMeta.FAMILY(), TSMeta.COUNTER_QUALIFIER(), Bytes.fromLong(1)); client.put(tracking); - } else if (config.enable_tsuid_incrementing() && config.enable_realtime_ts()) { - TSMeta.incrementAndGetCounter(TSDB.this, tsuid); - } else if (!config.enable_tsuid_incrementing() && config.enable_realtime_ts()) { - TSMeta.storeIfNecessary(TSDB.this, tsuid); + } } } From 4675b10d6dca897e49ed4bb274e027433f5eca3d Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Mon, 18 Apr 2016 17:30:21 -0700 Subject: [PATCH 175/233] Fix indentation in TSDB around the meta data incrementing. Signed-off-by: Chris Larsen --- src/core/TSDB.java | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/core/TSDB.java b/src/core/TSDB.java index 2842b89067..7ea1a79d37 100644 --- a/src/core/TSDB.java +++ b/src/core/TSDB.java @@ -908,18 +908,18 @@ private Deferred addPointInternal(final String metric, if (meta_cache != null) { meta_cache.increment(tsuid); } else { - if (config.enable_tsuid_tracking()) { - if (config.enable_realtime_ts()) { - if (config.enable_tsuid_incrementing()) { - TSMeta.incrementAndGetCounter(TSDB.this, tsuid); + if (config.enable_tsuid_tracking()) { + if (config.enable_realtime_ts()) { + if (config.enable_tsuid_incrementing()) { + TSMeta.incrementAndGetCounter(TSDB.this, tsuid); + } else { + TSMeta.storeIfNecessary(TSDB.this, tsuid); + } } else { - TSMeta.storeIfNecessary(TSDB.this, tsuid); + final PutRequest tracking = new PutRequest(meta_table, tsuid, + TSMeta.FAMILY(), TSMeta.COUNTER_QUALIFIER(), Bytes.fromLong(1)); + client.put(tracking); } - } else { - final PutRequest tracking = new PutRequest(meta_table, tsuid, - TSMeta.FAMILY(), TSMeta.COUNTER_QUALIFIER(), Bytes.fromLong(1)); - client.put(tracking); - } } } From 176c997af4bf34ce6d023a975367ec1c838b98a5 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Mon, 18 Apr 2016 17:39:23 -0700 Subject: [PATCH 176/233] Fix the filter metric and tag resolution chain by making sure the first callback is attached as a deferring callback and returns the proper type Signed-off-by: Chris Larsen --- src/core/TsdbQuery.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/TsdbQuery.java b/src/core/TsdbQuery.java index 105cecf8c2..7270c0c7c3 100644 --- a/src/core/TsdbQuery.java +++ b/src/core/TsdbQuery.java @@ -365,14 +365,14 @@ class FilterCB implements Callback> { @Override public Object call(final ArrayList results) throws Exception { findGroupBys(); - return Deferred.fromResult(null); + return null; } } /** Resolve and group by tags after resolving the metric */ - class MetricCB implements Callback { + class MetricCB implements Callback, byte[]> { @Override - public Object call(final byte[] uid) throws Exception { + public Deferred call(final byte[] uid) throws Exception { metric = uid; if (filters != null) { final List> deferreds = @@ -389,7 +389,7 @@ public Object call(final byte[] uid) throws Exception { // fire off the callback chain by resolving the metric first return tsdb.metrics.getIdAsync(sub_query.getMetric()) - .addCallback(new MetricCB()); + .addCallbackDeferring(new MetricCB()); } } From bd327c1a72a0cef60bea43981c966b4aed013f98 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Mon, 18 Apr 2016 18:05:27 -0700 Subject: [PATCH 177/233] Remove typed exceptions from the StartupPlugin abstract and add some Javadocs. Also rename the TSDB Startup Plugin accessors for clarity and modify formatting. Also revert API change in 5cdbf14dbbc50d0ebc06551f343305a447913261. However it will now rethrow the Exception as a RuntimeException while still logging the original exception. Signed-off-by: Chris Larsen --- src/core/TSDB.java | 30 +++++++++++++++++------------- src/tools/StartupPlugin.java | 9 +++++---- src/tools/TSDMain.java | 2 +- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/core/TSDB.java b/src/core/TSDB.java index 7ea1a79d37..9cd291c7c6 100644 --- a/src/core/TSDB.java +++ b/src/core/TSDB.java @@ -247,11 +247,12 @@ public static byte[] FAMILY() { * Called by initializePlugins, also used to load startup plugins. * @since 2.3 */ - public static void loadPluginPath(final String plugin_path) throws RuntimeException { + public static void loadPluginPath(final String plugin_path) { if (plugin_path != null && !plugin_path.isEmpty()) { try { PluginLoader.loadJARs(plugin_path); } catch (Exception e) { + LOG.error("Error loading plugins from plugin path: " + plugin_path, e); throw new RuntimeException("Error loading plugins from plugin path: " + plugin_path, e); } @@ -268,13 +269,9 @@ public static void loadPluginPath(final String plugin_path) throws RuntimeExcept * @throws IllegalArgumentException if a plugin could not be initialized * @since 2.0 */ - public void initializePlugins(final boolean init_rpcs) throws RuntimeException { + public void initializePlugins(final boolean init_rpcs) { final String plugin_path = config.getString("tsd.core.plugin_path"); - try { - loadPluginPath(plugin_path); - } catch (RuntimeException e) { - throw e; - } + loadPluginPath(plugin_path); try { TagVFilter.initializeFilterMap(this); @@ -389,17 +386,24 @@ public final HBaseClient getClient() { } /** - * Sets the startup plugin so that it can be shutdown properly. - * @param startup + * Sets the startup plugin so that it can be shutdown properly. + * Note that this method will not initialize or call any other methods + * belonging to the plugin's implementation. + * @param plugin The startup plugin that was used. * @since 2.3 */ - public final void setStartup(StartupPlugin startup) { this.startup = startup; } + public final void setStartupPlugin(final StartupPlugin plugin) { + startup = plugin; + } + /** - * Getter that returns the startup plugin object - * @return The StartupPlugin object + * Getter that returns the startup plugin object. + * @return The StartupPlugin object or null if the plugin was not set. * @since 2.3 */ - public final StartupPlugin getStartup() { return this.startup; } + public final StartupPlugin getStartupPlugin() { + return startup; + } /** * Getter that returns the configuration object diff --git a/src/tools/StartupPlugin.java b/src/tools/StartupPlugin.java index 3c56464a68..cbfa040522 100644 --- a/src/tools/StartupPlugin.java +++ b/src/tools/StartupPlugin.java @@ -1,5 +1,5 @@ // This file is part of OpenTSDB. -// Copyright (C) 2013 The OpenTSDB Authors. +// Copyright (C) 2016 The OpenTSDB Authors. // // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published by @@ -37,16 +37,17 @@ public abstract class StartupPlugin { * up properly. The TSD will then shutdown so the operator can fix the * problem. Please use IllegalArgumentException for configuration issues. * @param tsdb The parent TSDB object + * @return A reference to the same configuration object passed in the parameters + * on success. * @throws IllegalArgumentException if required configuration parameters are * missing - * @throws Exception if something else goes wrong */ - public abstract Config initialize(Config config) throws IllegalArgumentException, Exception; + public abstract Config initialize(Config config); /** * Called when the TSD is fully initialized and ready to handle traffic. */ - public abstract void setReady(final TSDB tsdb) throws Exception; + public abstract void setReady(final TSDB tsdb); /** * Called to gracefully shutdown the plugin. Implementations should close diff --git a/src/tools/TSDMain.java b/src/tools/TSDMain.java index 4036ae3015..d88b56a638 100644 --- a/src/tools/TSDMain.java +++ b/src/tools/TSDMain.java @@ -180,7 +180,7 @@ public static void main(String[] args) throws IOException { try { tsdb = new TSDB(config); if (startup != null) { - tsdb.setStartup(startup); + tsdb.setStartupPlugin(startup); } tsdb.initializePlugins(true); if (config.getBoolean("tsd.storage.hbase.prefetch_meta")) { From ed9a7410f55272d915a24cc4e9937a7c1ec97efe Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Mon, 18 Apr 2016 18:26:42 -0700 Subject: [PATCH 178/233] Little bit of cleanup in DateTime.parseDateTimeString() by removing a regex that isn't used and formatting the variable names per TSD spec. Signed-off-by: Chris Larsen --- src/utils/DateTime.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/utils/DateTime.java b/src/utils/DateTime.java index 33f3ab3db5..41c10ed805 100644 --- a/src/utils/DateTime.java +++ b/src/utils/DateTime.java @@ -133,15 +133,14 @@ public static final long parseDateTimeString(final String datetime, } else { try { long time; - Boolean containsDot = datetime.contains("."); + final boolean contains_dot = datetime.contains("."); // [0-9]{10} ten digits // \\. a dot // [0-9]{1,3} one to three digits - Boolean isValidDottedMillesecond = datetime.matches("^[0-9]{10}\\.[0-9]{1,3}$"); - // one to ten digits (0-9) - Boolean isValidSeconds = datetime.matches("^[0-9]{1,10}$"); - if (containsDot) { - if (!isValidDottedMillesecond) { + final boolean valid_dotted_ms = + datetime.matches("^[0-9]{10}\\.[0-9]{1,3}$"); + if (contains_dot) { + if (!valid_dotted_ms) { throw new IllegalArgumentException("Invalid time: " + datetime + ". Millisecond timestamps must be in the format " + ". where the milliseconds are limited to 3 digits"); From c55e9b34212e255bb73129d8914954d8524469e5 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Mon, 18 Apr 2016 19:20:32 -0700 Subject: [PATCH 179/233] Fix up d46133405b98df52ccb1ad6c0176897c2728f67c. Rollback the breaking API changes by adding overloaded ctors. On testing, DefaultChannelGroup.size() doesn't return the current size. It often returns "1" even though there may be 5 open connections from the same host to the TSD. This could be an issue in the Netty implementation or something else. Anyway now we're using an AtomicInteger that will increment and decrement on each channel open and close, respectively. It's ugly but accurate. Also modify the naming styles to fit in with TSD code. Signed-off-by: Chris Larsen --- src/tools/TSDMain.java | 6 +-- src/tsd/ConnectionManager.java | 72 +++++++++++++++++++++++++++------- src/tsd/PipelineFactory.java | 39 ++++++++++++------ 3 files changed, 89 insertions(+), 28 deletions(-) diff --git a/src/tools/TSDMain.java b/src/tools/TSDMain.java index d88b56a638..0c6f5d55d4 100644 --- a/src/tools/TSDMain.java +++ b/src/tools/TSDMain.java @@ -141,9 +141,9 @@ public static void main(String[] args) throws IOException { } final ServerSocketChannelFactory factory; - int connectionsLimit = 0; + int connections_limit = 0; try { - connectionsLimit = config.getInt("tsd.core.connections.limit"); + connections_limit = config.getInt("tsd.core.connections.limit"); } catch (NumberFormatException nfe) { usage(argp, "Invalid connections limit", 1); } @@ -197,7 +197,7 @@ public static void main(String[] args) throws IOException { // here to fail fast. final RpcManager manager = RpcManager.instance(tsdb); - server.setPipelineFactory(new PipelineFactory(tsdb, manager, connectionsLimit)); + server.setPipelineFactory(new PipelineFactory(tsdb, manager, connections_limit)); if (config.hasProperty("tsd.network.backlog")) { server.setOption("backlog", config.getInt("tsd.network.backlog")); } diff --git a/src/tsd/ConnectionManager.java b/src/tsd/ConnectionManager.java index f4d4523e3a..902c31315b 100644 --- a/src/tsd/ConnectionManager.java +++ b/src/tsd/ConnectionManager.java @@ -14,12 +14,14 @@ import java.io.IOException; import java.nio.channels.ClosedChannelException; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelEvent; +import org.jboss.netty.channel.ChannelException; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelStateEvent; import org.jboss.netty.channel.ExceptionEvent; @@ -42,10 +44,15 @@ final class ConnectionManager extends SimpleChannelHandler { private static final AtomicLong exceptions_closed = new AtomicLong(); private static final AtomicLong exceptions_reset = new AtomicLong(); private static final AtomicLong exceptions_timeout = new AtomicLong(); - /** - * max connections can be serviced by tsd, if over limit, tsd will close new connection. - */ - private int connectionsLimit; + + /** Max connections can be serviced by tsd, if over limit, tsd will refuse + * new connections. */ + private final int connections_limit; + + /** A counter used for determining how many channels are open. Something odd + * happens with the DefaultChannelGroup in that .size() doesn't return the + * actual number of open connections. TODO - find out why. */ + private final AtomicInteger open_connections = new AtomicInteger(); private static final DefaultChannelGroup channels = new DefaultChannelGroup("all-channels"); @@ -54,10 +61,21 @@ static void closeAllConnections() { channels.close().awaitUninterruptibly(); } - /** Constructor. */ - public ConnectionManager(int connectionsLimit) { - LOG.info("totalConnections limit is set : " + connectionsLimit); - this.connectionsLimit = connectionsLimit; + /** + * Default Ctor with no concurrent connection limit. + */ + public ConnectionManager() { + connections_limit = 0; + } + + /** + * CTor for setting a limit on concurrent connections. + * @param connections_limit The maximum number of concurrent connections allowed. + * @since 2.3 + */ + public ConnectionManager(final int connections_limit) { + LOG.info("TSD concurrent connection limit set to: " + connections_limit); + this.connections_limit = connections_limit; } /** @@ -83,18 +101,24 @@ public static void collectStats(final StatsCollector collector) { @Override public void channelOpen(final ChannelHandlerContext ctx, final ChannelStateEvent e) throws IOException { - if (connectionsLimit > 0) { - int channelSize = channels.size(); - if (channelSize >= connectionsLimit) { - e.getChannel().close(); - connections_rejected.incrementAndGet(); - throw new IOException("Channel size (" + channelSize + ") exceeds total connection limit (" + connectionsLimit + ")"); + if (connections_limit > 0) { + final int channel_size = open_connections.incrementAndGet(); + if (channel_size > connections_limit) { + throw new ConnectionRefusedException("Channel size (" + channel_size + ") exceeds total " + + "connection limit (" + connections_limit + ")"); + // exceptionCaught will close the connection and increment the counter. } } channels.add(e.getChannel()); connections_established.incrementAndGet(); } + @Override + public void channelClosed(final ChannelHandlerContext ctx, + final ChannelStateEvent e) throws IOException { + open_connections.decrementAndGet(); + } + @Override public void handleUpstream(final ChannelHandlerContext ctx, final ChannelEvent e) throws Exception { @@ -126,6 +150,13 @@ public void exceptionCaught(final ChannelHandlerContext ctx, // in Java. Like, people have been bitching about errno for years, // and Java managed to do something *far* worse. That's quite a feat. return; + } else if (cause instanceof ConnectionRefusedException) { + connections_rejected.incrementAndGet(); + if (LOG.isDebugEnabled()) { + LOG.debug("Refusing connection from " + chan, e.getCause()); + } + chan.close(); + return; } } if (cause instanceof CodecEmbedderException) { @@ -139,4 +170,17 @@ public void exceptionCaught(final ChannelHandlerContext ctx, e.getChannel().close(); } + /** Simple exception for refusing a connection. */ + private static class ConnectionRefusedException extends ChannelException { + + /** + * Default ctor with a message. + * @param message A descriptive message for the exception. + */ + public ConnectionRefusedException(final String message) { + super(message); + } + + private static final long serialVersionUID = 5348377149312597939L; + } } diff --git a/src/tsd/PipelineFactory.java b/src/tsd/PipelineFactory.java index 30389c34c6..05414f4491 100644 --- a/src/tsd/PipelineFactory.java +++ b/src/tsd/PipelineFactory.java @@ -14,8 +14,6 @@ import static org.jboss.netty.channel.Channels.pipeline; -import java.util.concurrent.ThreadFactory; - import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelHandler; @@ -30,7 +28,6 @@ import org.jboss.netty.handler.codec.http.HttpRequestDecoder; import org.jboss.netty.handler.codec.http.HttpResponseEncoder; import org.jboss.netty.handler.timeout.IdleStateHandler; -import org.jboss.netty.util.HashedWheelTimer; import org.jboss.netty.util.Timer; import net.opentsdb.core.TSDB; @@ -38,6 +35,9 @@ /** * Creates a newly configured {@link ChannelPipeline} for a new channel. * This class is supposed to be a singleton. + * NOTE: On creation (as of 2.3) the property given in the config for + * "tsd.core.connections.limit" will be used to limit the number of concurrent + * connections supported by the pipeline. The default is zero. */ public final class PipelineFactory implements ChannelPipelineFactory { @@ -70,7 +70,8 @@ public final class PipelineFactory implements ChannelPipelineFactory { * serializers */ public PipelineFactory(final TSDB tsdb) { - this(tsdb, RpcManager.instance(tsdb), tsdb.getConfig().getInt("tsd.core.connections.limit")); + this(tsdb, RpcManager.instance(tsdb), + tsdb.getConfig().getInt("tsd.core.connections.limit")); } /** @@ -79,16 +80,32 @@ public PipelineFactory(final TSDB tsdb) { * @param tsdb The TSDB to use. * @param manager instance of a ready-to-use {@link RpcManager}. * @throws RuntimeException if there is an issue loading plugins - * @throws Exception if the HttpQuery handler is unable to load - * serializers + * @throws Exception if the HttpQuery handler is unable to load serializers + */ + public PipelineFactory(final TSDB tsdb, final RpcManager manager) { + this(tsdb, RpcManager.instance(tsdb), + tsdb.getConfig().getInt("tsd.core.connections.limit")); + } + + /** + * Constructor that initializes the RPC router and loads HTTP formatter + * plugins using an already-configured {@link RpcManager}. + * @param tsdb The TSDB to use. + * @param manager instance of a ready-to-use {@link RpcManager}. + * @param connections_limit The maximum number of concurrent connections + * supported by the TSD. + * @throws RuntimeException if there is an issue loading plugins + * @throws Exception if the HttpQuery handler is unable to load serializers + * @since 2.3 */ - public PipelineFactory(final TSDB tsdb, final RpcManager manager, final int connectionsLimit) { + public PipelineFactory(final TSDB tsdb, final RpcManager manager, + final int connections_limit) { this.tsdb = tsdb; - this.socketTimeout = tsdb.getConfig().getInt("tsd.core.socket.timeout"); + socketTimeout = tsdb.getConfig().getInt("tsd.core.socket.timeout"); timer = tsdb.getTimer(); - this.timeoutHandler = new IdleStateHandler(timer, 0, 0, this.socketTimeout); - this.rpchandler = new RpcHandler(tsdb, manager); - this.connmgr = new ConnectionManager(connectionsLimit); + timeoutHandler = new IdleStateHandler(timer, 0, 0, socketTimeout); + rpchandler = new RpcHandler(tsdb, manager); + connmgr = new ConnectionManager(connections_limit); try { HttpQuery.initializeSerializerMaps(tsdb); } catch (RuntimeException e) { From 005f186d044000ddf8ecc96446e2dee8c174ba54 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Tue, 19 Apr 2016 13:01:44 -0700 Subject: [PATCH 180/233] Add TagVFilter.getCopy() for creating a duplicate of the filter. Signed-off-by: Chris Larsen --- src/query/filter/TagVFilter.java | 12 ++++++++++++ test/query/filter/TestTagVFilter.java | 17 +++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/query/filter/TagVFilter.java b/src/query/filter/TagVFilter.java index 0c5e797b22..611a6c8266 100644 --- a/src/query/filter/TagVFilter.java +++ b/src/query/filter/TagVFilter.java @@ -498,11 +498,23 @@ public byte[] getTagkBytes() { return tagk_bytes; } + /** @return a non-null list of tag value UIDs. May be empty. */ @JsonIgnore public List getTagVUids() { return tagv_uids == null ? Collections.emptyList() : tagv_uids; } + /** @return A copy of this filter BEFORE tag resolution, as a new object. */ + @JsonIgnore + public TagVFilter getCopy() { + return Builder() + .setFilter(filter) + .setTagk(tagk) + .setType(getType()) + .setGroupBy(group_by) + .build(); + } + /** @return whether or not to group by the results of this filter */ @JsonIgnore public boolean isGroupBy() { diff --git a/test/query/filter/TestTagVFilter.java b/test/query/filter/TestTagVFilter.java index 477f65aaa9..5c952c83df 100644 --- a/test/query/filter/TestTagVFilter.java +++ b/test/query/filter/TestTagVFilter.java @@ -15,6 +15,7 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -400,5 +401,21 @@ public void tagsToFiltersSameTagDiffValues() throws Exception { assertEquals(2, filters.size()); } + @Test + public void getCopy() { + final TagVFilter filter = TagVFilter.Builder() + .setFilter("*") + .setTagk(TAGK_STRING) + .setType("wildcard") + .setGroupBy(true) + .build(); + final TagVFilter copy = filter.getCopy(); + assertNotSame(filter, copy); + assertEquals(filter.filter, copy.filter); + assertEquals(filter.tagk, copy.tagk); + assertEquals(filter.getType(), copy.getType()); + assertEquals(filter.group_by, copy.group_by); + } + // TODO - test the plugin loader similar to the other plugins } From d1ec89faa7247d69de71605ad1e99372a981d167 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Tue, 19 Apr 2016 13:06:01 -0700 Subject: [PATCH 181/233] Fix concurrent modification exceptions with tag filters used in the /api/query/exp endpoint. Signed-off-by: Chris Larsen --- src/tsd/QueryExecutor.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/tsd/QueryExecutor.java b/src/tsd/QueryExecutor.java index 20e81bd76b..22937fcb4b 100644 --- a/src/tsd/QueryExecutor.java +++ b/src/tsd/QueryExecutor.java @@ -49,6 +49,7 @@ import net.opentsdb.query.expression.NumericFillPolicy; import net.opentsdb.query.expression.TimeSyncedIterator; import net.opentsdb.query.expression.VariableIterator.SetOperator; +import net.opentsdb.query.filter.TagVFilter; import net.opentsdb.query.pojo.Expression; import net.opentsdb.query.pojo.Filter; import net.opentsdb.query.pojo.Metric; @@ -156,21 +157,30 @@ public QueryExecutor(final TSDB tsdb, final Query query) { // filters if (mq.getFilter() != null && !mq.getFilter().isEmpty()) { - Filter filters = null; + List filters = null; + boolean explicit_tags = false; if (query.getFilters() == null || query.getFilters().isEmpty()) { throw new IllegalArgumentException("No filter defined: " + mq.getFilter()); } for (final Filter filter : query.getFilters()) { if (filter.getId().equals(mq.getFilter())) { - filters = filter; + // TODO - it'd be more efficient if we could share the filters but + // for now, this is the only way to avoid concurrent modifications. + filters = new ArrayList(filter.getTags().size()); + for (final TagVFilter f : filter.getTags()) { + filters.add(f.getCopy()); + } + explicit_tags = filter.getExplicitTags(); break; } } sub.setRate(timespan.isRate()); - sub.setFilters(filters.getTags()); sub.setAggregator( mq.getAggregator() != null ? mq.getAggregator() : timespan.getAggregator()); - sub.setExplicitTags(filters.getExplicitTags()); + if (filters != null) { + sub.setFilters(filters); + sub.setExplicitTags(explicit_tags); + } } } From 5c4e9b3bd2a61d6fd421233f31f50772888e43dd Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Fri, 22 Apr 2016 13:07:45 -0700 Subject: [PATCH 182/233] Add the WriteableDataPointFilterPlugin and UniqueIdFilterPlugin plugin interfaces. These can be used to prevent time series from being written or UIDs from being assigned. It can also enforce naming standards. Signed-off-by: Chris Larsen --- Makefile.am | 3 + src/core/IncomingDataPoints.java | 131 ++-- src/core/TSDB.java | 227 ++++-- src/core/Tags.java | 30 +- src/core/WriteableDataPointFilterPlugin.java | 99 +++ src/uid/FailedToAssignUniqueIdException.java | 18 + src/uid/UniqueId.java | 129 +++- src/uid/UniqueIdFilterPlugin.java | 101 +++ src/utils/Config.java | 2 + test/core/BaseTsdbTest.java | 12 + test/core/TestTSDB.java | 629 ----------------- test/core/TestTSDBAddPoint.java | 690 +++++++++++++++++++ test/core/TestTags.java | 55 +- test/uid/TestUniqueId.java | 127 +++- 14 files changed, 1485 insertions(+), 768 deletions(-) create mode 100644 src/core/WriteableDataPointFilterPlugin.java create mode 100644 src/uid/UniqueIdFilterPlugin.java create mode 100644 test/core/TestTSDBAddPoint.java diff --git a/Makefile.am b/Makefile.am index 6f274a2373..02ebd0e50d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -68,6 +68,7 @@ tsdb_SRC := \ src/core/TSQuery.java \ src/core/TSSubQuery.java \ src/core/WritableDataPoints.java \ + src/core/WriteableDataPointFilterPlugin.java \ src/graph/Plot.java \ src/meta/Annotation.java \ src/meta/MetaDataCache.java \ @@ -179,6 +180,7 @@ tsdb_SRC := \ src/uid/NoSuchUniqueName.java \ src/uid/RandomUniqueId.java \ src/uid/UniqueId.java \ + src/uid/UniqueIdFilterPlugin.java \ src/uid/UniqueIdInterface.java \ src/utils/ByteArrayPair.java \ src/utils/ByteSet.java \ @@ -256,6 +258,7 @@ test_SRC := \ test/core/TestSpanGroup.java \ test/core/TestTags.java \ test/core/TestTSDB.java \ + test/core/TestTSDBAddDataPoint.java \ test/core/TestTsdbQueryDownsample.java \ test/core/TestTsdbQueryDownsampleSalted.java \ test/core/TestTsdbQuery.java \ diff --git a/src/core/IncomingDataPoints.java b/src/core/IncomingDataPoints.java index 6fb9c9ba23..6f6245b780 100644 --- a/src/core/IncomingDataPoints.java +++ b/src/core/IncomingDataPoints.java @@ -73,6 +73,12 @@ final class IncomingDataPoints implements WritableDataPoints { /** Are we doing a batch import? */ private boolean batch_import; + + /** The metric for this time series */ + private String metric; + + /** Copy of the tags given us by the caller */ + private Map tags; /** * Constructor. @@ -163,7 +169,7 @@ static Deferred rowKeyTemplateAsync(final TSDB tsdb, // Lookup or create the metric ID. final Deferred metric_id; if (tsdb.config.auto_metric()) { - metric_id = tsdb.metrics.getOrCreateIdAsync(metric); + metric_id = tsdb.metrics.getOrCreateIdAsync(metric, metric, tags); } else { metric_id = tsdb.metrics.getIdAsync(metric); } @@ -193,8 +199,8 @@ public Deferred call(final ArrayList tags) { } // Kick off the resolution of all tags. - return Tags.resolveOrCreateAllAsync(tsdb, tags).addCallbackDeferring( - new CopyTagsInRowKeyCB()); + return Tags.resolveOrCreateAllAsync(tsdb, metric, tags) + .addCallbackDeferring(new CopyTagsInRowKeyCB()); } public void setSeries(final String metric, final Map tags) { @@ -207,6 +213,8 @@ public void setSeries(final String metric, final Map tags) { } catch (Exception e) { throw new RuntimeException("Should never happen", e); } + this.metric = metric; + this.tags = tags; size = 0; } @@ -281,57 +289,80 @@ private Deferred addPointInternal(final long timestamp, + " when trying to add value=" + Arrays.toString(value) + " to " + this); } - last_ts = (ms_timestamp ? timestamp : timestamp * 1000); + + /** Callback executed for chaining filter calls to see if the value + * should be written or not. */ + final class WriteCB implements Callback, Boolean> { + @Override + public Deferred call(final Boolean allowed) throws Exception { + if (!allowed) { + return Deferred.fromResult(null); + } + - long base_time = baseTime(); - long incoming_base_time; - if (ms_timestamp) { - // drop the ms timestamp to seconds to calculate the base timestamp - incoming_base_time = ((timestamp / 1000) - ((timestamp / 1000) % Const.MAX_TIMESPAN)); - } else { - incoming_base_time = (timestamp - (timestamp % Const.MAX_TIMESPAN)); - } + last_ts = (ms_timestamp ? timestamp : timestamp * 1000); - if (incoming_base_time - base_time >= Const.MAX_TIMESPAN) { - // Need to start a new row as we've exceeded Const.MAX_TIMESPAN. - base_time = updateBaseTime((ms_timestamp ? timestamp / 1000 : timestamp)); - } + long base_time = baseTime(); + long incoming_base_time; + if (ms_timestamp) { + // drop the ms timestamp to seconds to calculate the base timestamp + incoming_base_time = ((timestamp / 1000) - ((timestamp / 1000) % Const.MAX_TIMESPAN)); + } else { + incoming_base_time = (timestamp - (timestamp % Const.MAX_TIMESPAN)); + } - // Java is so stupid with its auto-promotion of int to float. - final byte[] qualifier = Internal.buildQualifier(timestamp, flags); - - // TODO(tsuna): The following timing is rather useless. First of all, - // the histogram never resets, so it tends to converge to a certain - // distribution and never changes. What we really want is a moving - // histogram so we can see how the latency distribution varies over time. - // The other problem is that the Histogram class isn't thread-safe and - // here we access it from a callback that runs in an unknown thread, so - // we might miss some increments. So let's comment this out until we - // have a proper thread-safe moving histogram. - // final long start_put = System.nanoTime(); - // final Callback cb = new Callback() { - // public Object call(final Object arg) { - // putlatency.add((int) ((System.nanoTime() - start_put) / 1000000)); - // return arg; - // } - // public String toString() { - // return "time put request"; - // } - // }; - - // TODO(tsuna): Add an errback to handle some error cases here. - if (tsdb.getConfig().enable_appends()) { - final AppendDataPoints kv = new AppendDataPoints(qualifier, value); - final AppendRequest point = new AppendRequest(tsdb.table, row, TSDB.FAMILY, - AppendDataPoints.APPEND_COLUMN_QUALIFIER, kv.getBytes()); - point.setDurable(!batch_import); - return tsdb.client.append(point);/* .addBoth(cb) */ - } else { - final PutRequest point = new PutRequest(tsdb.table, row, TSDB.FAMILY, - qualifier, value); - point.setDurable(!batch_import); - return tsdb.client.put(point)/* .addBoth(cb) */; + if (incoming_base_time - base_time >= Const.MAX_TIMESPAN) { + // Need to start a new row as we've exceeded Const.MAX_TIMESPAN. + base_time = updateBaseTime((ms_timestamp ? timestamp / 1000 : timestamp)); + } + + // Java is so stupid with its auto-promotion of int to float. + final byte[] qualifier = Internal.buildQualifier(timestamp, flags); + + // TODO(tsuna): The following timing is rather useless. First of all, + // the histogram never resets, so it tends to converge to a certain + // distribution and never changes. What we really want is a moving + // histogram so we can see how the latency distribution varies over time. + // The other problem is that the Histogram class isn't thread-safe and + // here we access it from a callback that runs in an unknown thread, so + // we might miss some increments. So let's comment this out until we + // have a proper thread-safe moving histogram. + // final long start_put = System.nanoTime(); + // final Callback cb = new Callback() { + // public Object call(final Object arg) { + // putlatency.add((int) ((System.nanoTime() - start_put) / 1000000)); + // return arg; + // } + // public String toString() { + // return "time put request"; + // } + // }; + + // TODO(tsuna): Add an errback to handle some error cases here. + if (tsdb.getConfig().enable_appends()) { + final AppendDataPoints kv = new AppendDataPoints(qualifier, value); + final AppendRequest point = new AppendRequest(tsdb.table, row, TSDB.FAMILY, + AppendDataPoints.APPEND_COLUMN_QUALIFIER, kv.getBytes()); + point.setDurable(!batch_import); + return tsdb.client.append(point);/* .addBoth(cb) */ + } else { + final PutRequest point = new PutRequest(tsdb.table, row, TSDB.FAMILY, + qualifier, value); + point.setDurable(!batch_import); + return tsdb.client.put(point)/* .addBoth(cb) */; + } + } + @Override + public String toString() { + return "IncomingDataPoints.addPointInternal Write Callback"; + } + } + + if (tsdb.getTSfilter() != null && tsdb.getTSfilter().filterDataPoints()) { + return tsdb.getTSfilter().allowDataPoint(metric, timestamp, value, tags, flags) + .addCallbackDeferring(new WriteCB()); } + return Deferred.fromResult(true).addCallbackDeferring(new WriteCB()); } private void grow() { diff --git a/src/core/TSDB.java b/src/core/TSDB.java index 9cd291c7c6..ba6839715c 100644 --- a/src/core/TSDB.java +++ b/src/core/TSDB.java @@ -48,6 +48,7 @@ import net.opentsdb.uid.NoSuchUniqueId; import net.opentsdb.uid.NoSuchUniqueName; import net.opentsdb.uid.UniqueId; +import net.opentsdb.uid.UniqueIdFilterPlugin; import net.opentsdb.uid.UniqueId.UniqueIdType; import net.opentsdb.utils.Config; import net.opentsdb.utils.DateTime; @@ -134,6 +135,16 @@ public final class TSDB { /** Plugin for dealing with data points that can't be stored */ private StorageExceptionHandler storage_exception_handler = null; + /** A filter plugin for allowing or blocking time series */ + private WriteableDataPointFilterPlugin ts_filter; + + /** A filter plugin for allowing or blocking UIDs */ + private UniqueIdFilterPlugin uid_filter; + + /** Writes rejected by the filter */ + private final AtomicLong rejected_dps = new AtomicLong(); + private final AtomicLong rejected_aggregate_dps = new AtomicLong(); + /** Datapoints Added */ private static final AtomicLong datapoints_added = new AtomicLong(); @@ -194,17 +205,13 @@ public TSDB(final HBaseClient client, final Config config) { meta_table = config.getString("tsd.storage.hbase.meta_table").getBytes(CHARSET); if (config.getBoolean("tsd.core.uid.random_metrics")) { - metrics = new UniqueId(this.client, uidtable, METRICS_QUAL, METRICS_WIDTH, - true); + metrics = new UniqueId(this, uidtable, METRICS_QUAL, METRICS_WIDTH, true); } else { - metrics = new UniqueId(this.client, uidtable, METRICS_QUAL, METRICS_WIDTH); + metrics = new UniqueId(this, uidtable, METRICS_QUAL, METRICS_WIDTH, false); } - tag_names = new UniqueId(this.client, uidtable, TAG_NAME_QUAL, TAG_NAME_WIDTH); - tag_values = new UniqueId(this.client, uidtable, TAG_VALUE_QUAL, TAG_VALUE_WIDTH); + tag_names = new UniqueId(this, uidtable, TAG_NAME_QUAL, TAG_NAME_WIDTH, false); + tag_values = new UniqueId(this, uidtable, TAG_VALUE_QUAL, TAG_VALUE_WIDTH, false); compactionq = new CompactionQueue(this); - metrics.setTSDB(this); - tag_names.setTSDB(this); - tag_values.setTSDB(this); if (config.hasProperty("tsd.core.timezone")) { DateTime.setDefaultTimezone(config.getString("tsd.core.timezone")); @@ -374,6 +381,48 @@ public void initializePlugins(final boolean init_rpcs) { storage_exception_handler.getClass().getCanonicalName() + "] version: " + storage_exception_handler.version()); } + + // Writeable Data Point Filter + if (config.getBoolean("tsd.timeseriesfilter.enable")) { + ts_filter = PluginLoader.loadSpecificPlugin( + config.getString("tsd.timeseriesfilter.plugin"), + WriteableDataPointFilterPlugin.class); + if (ts_filter == null) { + throw new IllegalArgumentException( + "Unable to locate time series filter plugin plugin: " + + config.getString("tsd.timeseriesfilter.plugin")); + } + try { + ts_filter.initialize(this); + } catch (Exception e) { + throw new RuntimeException( + "Failed to initialize time series filter plugin", e); + } + LOG.info("Successfully initialized time series filter plugin [" + + ts_filter.getClass().getCanonicalName() + "] version: " + + ts_filter.version()); + } + + // UID Filter + if (config.getBoolean("tsd.uidfilter.enable")) { + uid_filter = PluginLoader.loadSpecificPlugin( + config.getString("tsd.uidfilter.plugin"), + UniqueIdFilterPlugin.class); + if (uid_filter == null) { + throw new IllegalArgumentException( + "Unable to locate UID filter plugin plugin: " + + config.getString("tsd.uidfilter.plugin")); + } + try { + uid_filter.initialize(this); + } catch (Exception e) { + throw new RuntimeException( + "Failed to initialize UID filter plugin", e); + } + LOG.info("Successfully initialized UID filter plugin [" + + uid_filter.getClass().getCanonicalName() + "] version: " + + uid_filter.version()); + } } /** @@ -423,6 +472,22 @@ public final StorageExceptionHandler getStorageExceptionHandler() { return storage_exception_handler; } + /** + * @return the TS filter object, may be null + * @since 2.3 + */ + public WriteableDataPointFilterPlugin getTSfilter() { + return ts_filter; + } + + /** + * @return The UID filter object, may be null. + * @since 2.3 + */ + public UniqueIdFilterPlugin getUidFilter() { + return uid_filter; + } + /** * Attempts to find the name for a unique identifier given a type * @param type The type of UID @@ -583,6 +648,10 @@ public void collectStats(final StatsCollector collector) { } catch (Exception e) { throw new RuntimeException("Shouldn't be here", e); } + + collector.record("uid.filter.rejected", rejected_dps.get(), "kind=raw"); + collector.record("uid.filter.rejected", rejected_aggregate_dps.get(), + "kind=aggregate"); { final Runtime runtime = Runtime.getRuntime(); @@ -671,6 +740,22 @@ public void collectStats(final StatsCollector collector) { collector.clearExtraTag("plugin"); } } + if (ts_filter != null) { + try { + collector.addExtraTag("plugin", "timeseriesFilter"); + ts_filter.collectStats(collector); + } finally { + collector.clearExtraTag("plugin"); + } + } + if (uid_filter != null) { + try { + collector.addExtraTag("plugin", "uidFilter"); + uid_filter.collectStats(collector); + } finally { + collector.clearExtraTag("plugin"); + } + } } /** Returns a latency histogram for Put RPCs used to store data points. */ @@ -695,6 +780,8 @@ private static void collectUidStats(final UniqueId uid, collector.record("uid.cache-size", uid.cacheSize(), "kind=" + uid.kind()); collector.record("uid.random-collisions", uid.randomIdCollisions(), "kind=" + uid.kind()); + collector.record("uid.rejected-assignments", uid.rejectedAssignments(), + "kind=" + uid.kind()); } /** @return the width, in bytes, of metric UIDs */ @@ -878,59 +965,81 @@ private Deferred addPointInternal(final String metric, base_time = (timestamp - (timestamp % Const.MAX_TIMESPAN)); } - Bytes.setInt(row, (int) base_time, metrics.width() + Const.SALT_WIDTH()); - RowKey.prefixKeyWithSalt(row); - - Deferred result = null; - if (config.enable_appends()) { - final AppendDataPoints kv = new AppendDataPoints(qualifier, value); - final AppendRequest point = new AppendRequest(table, row, FAMILY, - AppendDataPoints.APPEND_COLUMN_QUALIFIER, kv.getBytes()); - result = client.append(point); - } else { - scheduleForCompaction(row, (int) base_time); - final PutRequest point = new PutRequest(table, row, FAMILY, qualifier, value); - result = client.put(point); - } + /** Callback executed for chaining filter calls to see if the value + * should be written or not. */ + final class WriteCB implements Callback, Boolean> { + @Override + public Deferred call(final Boolean allowed) throws Exception { + if (!allowed) { + rejected_dps.incrementAndGet(); + return Deferred.fromResult(null); + } + + Bytes.setInt(row, (int) base_time, metrics.width() + Const.SALT_WIDTH()); + RowKey.prefixKeyWithSalt(row); + + Deferred result = null; + if (config.enable_appends()) { + final AppendDataPoints kv = new AppendDataPoints(qualifier, value); + final AppendRequest point = new AppendRequest(table, row, FAMILY, + AppendDataPoints.APPEND_COLUMN_QUALIFIER, kv.getBytes()); + result = client.append(point); + } else { + scheduleForCompaction(row, (int) base_time); + final PutRequest point = new PutRequest(table, row, FAMILY, qualifier, value); + result = client.put(point); + } - // Count all added datapoints, not just those that came in through PUT rpc - // Will there be others? Well, something could call addPoint programatically right? - datapoints_added.incrementAndGet(); + // Count all added datapoints, not just those that came in through PUT rpc + // Will there be others? Well, something could call addPoint programatically right? + datapoints_added.incrementAndGet(); - // TODO(tsuna): Add a callback to time the latency of HBase and store the - // timing in a moving Histogram (once we have a class for this). - - if (!config.enable_realtime_ts() && !config.enable_tsuid_incrementing() && - !config.enable_tsuid_tracking() && rt_publisher == null) { - return result; - } - - final byte[] tsuid = UniqueId.getTSUIDFromKey(row, METRICS_WIDTH, - Const.TIMESTAMP_BYTES); - - // if the meta cache plugin is instantiated then tracking goes through it - if (meta_cache != null) { - meta_cache.increment(tsuid); - } else { - if (config.enable_tsuid_tracking()) { - if (config.enable_realtime_ts()) { - if (config.enable_tsuid_incrementing()) { - TSMeta.incrementAndGetCounter(TSDB.this, tsuid); - } else { - TSMeta.storeIfNecessary(TSDB.this, tsuid); - } + // TODO(tsuna): Add a callback to time the latency of HBase and store the + // timing in a moving Histogram (once we have a class for this). + + if (!config.enable_realtime_ts() && !config.enable_tsuid_incrementing() && + !config.enable_tsuid_tracking() && rt_publisher == null) { + return result; + } + + final byte[] tsuid = UniqueId.getTSUIDFromKey(row, METRICS_WIDTH, + Const.TIMESTAMP_BYTES); + + // if the meta cache plugin is instantiated then tracking goes through it + if (meta_cache != null) { + meta_cache.increment(tsuid); } else { - final PutRequest tracking = new PutRequest(meta_table, tsuid, - TSMeta.FAMILY(), TSMeta.COUNTER_QUALIFIER(), Bytes.fromLong(1)); - client.put(tracking); + if (config.enable_tsuid_tracking()) { + if (config.enable_realtime_ts()) { + if (config.enable_tsuid_incrementing()) { + TSMeta.incrementAndGetCounter(TSDB.this, tsuid); + } else { + TSMeta.storeIfNecessary(TSDB.this, tsuid); + } + } else { + final PutRequest tracking = new PutRequest(meta_table, tsuid, + TSMeta.FAMILY(), TSMeta.COUNTER_QUALIFIER(), Bytes.fromLong(1)); + client.put(tracking); + } + } + } + + if (rt_publisher != null) { + rt_publisher.sinkDataPoint(metric, timestamp, value, tags, tsuid, flags); } + return result; + } + @Override + public String toString() { + return "addPointInternal Write Callback"; } } - - if (rt_publisher != null) { - rt_publisher.sinkDataPoint(metric, timestamp, value, tags, tsuid, flags); + + if (ts_filter != null && ts_filter.filterDataPoints()) { + return ts_filter.allowDataPoint(metric, timestamp, value, tags, flags) + .addCallbackDeferring(new WriteCB()); } - return result; + return Deferred.fromResult(true).addCallbackDeferring(new WriteCB()); } /** @@ -1078,6 +1187,16 @@ public Object call(ArrayList compactions) throws Exception { storage_exception_handler.getClass().getCanonicalName()); deferreds.add(storage_exception_handler.shutdown()); } + if (ts_filter != null) { + LOG.info("Shutting down time series filter plugin: " + + ts_filter.getClass().getCanonicalName()); + deferreds.add(ts_filter.shutdown()); + } + if (uid_filter != null) { + LOG.info("Shutting down UID filter plugin: " + + uid_filter.getClass().getCanonicalName()); + deferreds.add(uid_filter.shutdown()); + } // wait for plugins to shutdown before we close the client return deferreds.size() > 0 diff --git a/src/core/Tags.java b/src/core/Tags.java index 462196e152..6aa5cc2074 100644 --- a/src/core/Tags.java +++ b/src/core/Tags.java @@ -587,9 +587,9 @@ public static ArrayList resolveAll(final TSDB tsdb, */ public static Deferred> resolveAllAsync(final TSDB tsdb, final Map tags) { - return resolveAllInternalAsync(tsdb, tags, false); - } - + return resolveAllInternalAsync(tsdb, null, tags, false); + } + /** * Resolves (and creates, if necessary) all the tags (name=value) into the a * sorted byte arrays. @@ -626,7 +626,6 @@ static ArrayList resolveAllInternal(final TSDB tsdb, return tag_ids; } - /** * Resolves (and creates, if necessary) all the tags (name=value) into the a * sorted byte arrays. @@ -638,11 +637,28 @@ static ArrayList resolveAllInternal(final TSDB tsdb, */ static Deferred> resolveOrCreateAllAsync(final TSDB tsdb, final Map tags) { - return resolveAllInternalAsync(tsdb, tags, true); + return resolveAllInternalAsync(tsdb, null, tags, true); + } + + /** + * Resolves (and creates, if necessary) all the tags (name=value) into the a + * sorted byte arrays. + * @param tsdb The TSDB to use for UniqueId lookups. + * @param metric The metric associated with this tag set for filtering. + * @param tags The tags to resolve. If a new tag name or tag value is + * seen, it will be assigned an ID. + * @return an array of sorted tags (tag id, tag name). + * @since 2.3 + */ + static Deferred> + resolveOrCreateAllAsync(final TSDB tsdb, final String metric, + final Map tags) { + return resolveAllInternalAsync(tsdb, metric, tags, true); } private static Deferred> resolveAllInternalAsync(final TSDB tsdb, + final String metric, final Map tags, final boolean create) { final ArrayList> tag_ids = @@ -651,10 +667,10 @@ static ArrayList resolveAllInternal(final TSDB tsdb, // For each tag, start resolving the tag name and the tag value. for (final Map.Entry entry : tags.entrySet()) { final Deferred name_id = create - ? tsdb.tag_names.getOrCreateIdAsync(entry.getKey()) + ? tsdb.tag_names.getOrCreateIdAsync(entry.getKey(), metric, tags) : tsdb.tag_names.getIdAsync(entry.getKey()); final Deferred value_id = create - ? tsdb.tag_values.getOrCreateIdAsync(entry.getValue()) + ? tsdb.tag_values.getOrCreateIdAsync(entry.getValue(), metric, tags) : tsdb.tag_values.getIdAsync(entry.getValue()); // Then once the tag name is resolved, get the resolved tag value. diff --git a/src/core/WriteableDataPointFilterPlugin.java b/src/core/WriteableDataPointFilterPlugin.java new file mode 100644 index 0000000000..8a01159667 --- /dev/null +++ b/src/core/WriteableDataPointFilterPlugin.java @@ -0,0 +1,99 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2016 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.core; + +import java.util.Map; + +import com.stumbleupon.async.Deferred; + +import net.opentsdb.stats.StatsCollector; + +/** + * A filter that can determine whether or not time series should be allowed + * assignment based on their metric and tags. This is useful for such + * situations as: + *
  • Enforcing naming standards
  • + *
  • Blacklisting certain names or properties
  • + *
  • Preventing cardinality explosions
+ * Note: Implementations must have a parameterless constructor. The + * {@link #initialize(TSDB)} method will be called immediately after the plugin is + * instantiated and before any other methods are called. + * @since 2.3 + */ +public abstract class WriteableDataPointFilterPlugin { + + /** + * Called by TSDB to initialize the plugin + * Implementations are responsible for setting up any IO they need as well + * as starting any required background threads. + * Note: Implementations should throw exceptions if they can't start + * up properly. The TSD will then shutdown so the operator can fix the + * problem. Please use IllegalArgumentException for configuration issues. + * @param tsdb The parent TSDB object + * @throws IllegalArgumentException if required configuration parameters are + * missing + * @throws Exception if something else goes wrong + */ + public abstract void initialize(final TSDB tsdb); + + /** + * Called to gracefully shutdown the plugin. Implementations should close + * any IO they have open + * @return A deferred object that indicates the completion of the request. + * The {@link Object} has not special meaning and can be {@code null} + * (think of it as {@code Deferred}). + */ + public abstract Deferred shutdown(); + + /** + * Should return the version of this plugin in the format: + * MAJOR.MINOR.MAINT, e.g. "2.3.1". The MAJOR version should match the major + * version of OpenTSDB the plugin is meant to work with. + * @return A version string used to log the loaded version + */ + public abstract String version(); + + /** + * Called by the TSD when a request for statistics collection has come in. The + * implementation may provide one or more statistics. If no statistics are + * available for the implementation, simply stub the method. + * @param collector The collector used for emitting statistics + */ + public abstract void collectStats(final StatsCollector collector); + + /** + * Determine whether or not the data point should be stored. + * If the data should not be stored, the implementation can return false or an + * exception in the deferred object. Otherwise it should return true and the + * data point will be written to storage. + * @param metric The metric name for the data point + * @param timestamp The timestamp of the data + * @param value The value encoded as either an integer or floating point value + * @param tags The tags associated with the data point + * @param flags Encoding flags for the value + * @return True if the data should be written, false if it should be rejected. + */ + public abstract Deferred allowDataPoint( + final String metric, + final long timestamp, + final byte[] value, + final Map tags, + final short flags); + + /** + * Whether or not the filter should process data points. + * @return False if {@link #allowDataPoint(String, long, byte[], Map, short)} + * should NOT be called, true if it should. + */ + public abstract boolean filterDataPoints(); +} diff --git a/src/uid/FailedToAssignUniqueIdException.java b/src/uid/FailedToAssignUniqueIdException.java index 7ffa1a999d..fb79e6355c 100644 --- a/src/uid/FailedToAssignUniqueIdException.java +++ b/src/uid/FailedToAssignUniqueIdException.java @@ -28,6 +28,23 @@ public FailedToAssignUniqueIdException(final String kind, final String name, this.attempts = attempts; } + /** + * CTor + * @param kind The kind of object that couldn't be assigned + * @param name The name of the object that couldn't be assigned + * @param attempts How many attempts were made to assign + * @param msg A message to append + * @since 2.3 + */ + public FailedToAssignUniqueIdException(final String kind, final String name, + final int attempts, final String msg) { + super("Failed to assign ID for kind='" + kind + "' name='" + + name + "' after " + attempts + " attempts due to: " + msg); + this.kind = kind; + this.name = name; + this.attempts = attempts; + } + /** * CTor * @param kind The kind of object that couldn't be assigned @@ -44,6 +61,7 @@ public FailedToAssignUniqueIdException(final String kind, final String name, this.attempts = attempts; } + /** @return Returns the kind of unique ID that couldn't be assigned. */ public String kind() { return kind; diff --git a/src/uid/UniqueId.java b/src/uid/UniqueId.java index bd218900a4..666190d007 100644 --- a/src/uid/UniqueId.java +++ b/src/uid/UniqueId.java @@ -126,8 +126,10 @@ public enum UniqueIdType { /** How many times we collided with an existing ID when attempting to * generate a new UID */ private volatile int random_id_collisions; - - /** Whether or not to generate new UIDMetas */ + /** How many times assignments have been rejected by the UID filter */ + private volatile int rejected_assignments; + + /** TSDB object used for filtering and/or meta generation. */ private TSDB tsdb; /** @@ -170,6 +172,34 @@ public UniqueId(final HBaseClient client, final byte[] table, final String kind, this.id_width = (short) width; this.randomize_id = randomize_id; } + + /** + * Constructor. + * @param tsdb The TSDB this UID object belongs to + * @param table The name of the HBase table to use. + * @param kind The kind of Unique ID this instance will deal with. + * @param width The number of bytes on which Unique IDs should be encoded. + * @param Whether or not to randomize new UIDs + * @throws IllegalArgumentException if width is negative or too small/large + * or if kind is an empty string. + * @since 2.3 + */ + public UniqueId(final TSDB tsdb, final byte[] table, final String kind, + final int width, final boolean randomize_id) { + this.client = tsdb.getClient(); + this.tsdb = tsdb; + this.table = table; + if (kind.isEmpty()) { + throw new IllegalArgumentException("Empty string as 'kind' argument!"); + } + this.kind = toBytes(kind); + type = stringToUniqueIdType(kind); + if (width < 1 || width > 8) { + throw new IllegalArgumentException("Invalid width: " + width); + } + this.id_width = (short) width; + this.randomize_id = randomize_id; + } /** The number of times we avoided reading from HBase thanks to the cache. */ public int cacheHits() { @@ -191,6 +221,11 @@ public int randomIdCollisions() { return random_id_collisions; } + /** Returns the number of UID assignments rejected by the filter */ + public int rejectedAssignments() { + return rejected_assignments; + } + public String kind() { return fromBytes(kind); } @@ -754,6 +789,25 @@ public Boolean checkNameIsValid(final String name) throws RuntimeException { * @since 1.2 */ public Deferred getOrCreateIdAsync(final String name) { + return getOrCreateIdAsync(name, null, null); + } + + /** + * Finds the ID associated with a given name or creates it. + *

+ * The length of the byte array is fixed in advance by the implementation. + * + * @param name The name to lookup in the table or to assign an ID to. + * @param metric Name of the metric associated with the UID for filtering. + * @param tags Tag set associated with the UID for filtering. + * @throws HBaseException if there is a problem communicating with HBase. + * @throws IllegalStateException if all possible IDs are already assigned. + * @throws IllegalStateException if the ID found in HBase is encoded on the + * wrong number of bytes. + * @since 2.3 + */ + public Deferred getOrCreateIdAsync(final String name, + final String metric, final Map tags) { // Look in the cache first. final byte[] id = getIdFromCache(name); if (id != null) { @@ -762,29 +816,62 @@ public Deferred getOrCreateIdAsync(final String name) { } // Not found in our cache, so look in HBase instead. + /** Triggers the assignment if allowed through the filter */ + class AssignmentAllowedCB implements Callback, Boolean> { + @Override + public Deferred call(final Boolean allowed) throws Exception { + if (!allowed) { + rejected_assignments++; + return Deferred.fromError(new FailedToAssignUniqueIdException( + new String(kind), name, 0, "Blocked by UID filter.")); + } + + Deferred assignment = null; + synchronized (pending_assignments) { + assignment = pending_assignments.get(name); + if (assignment == null) { + // to prevent UID leaks that can be caused when multiple time + // series for the same metric or tags arrive, we need to write a + // deferred to the pending map as quickly as possible. Then we can + // start the assignment process after we've stashed the deferred + // and released the lock + assignment = new Deferred(); + pending_assignments.put(name, assignment); + } else { + LOG.info("Already waiting for UID assignment: " + name); + return assignment; + } + } + + // start the assignment dance after stashing the deferred + if (metric != null && LOG.isDebugEnabled()) { + LOG.debug("Assigning UID for '" + name + "' of type '" + type + + "' for series '" + metric + ", " + tags + "'"); + } + + // start the assignment dance after stashing the deferred + return new UniqueIdAllocator(name, assignment).tryAllocate(); + } + @Override + public String toString() { + return "AssignmentAllowedCB"; + } + } + + /** Triggers an assignment (possibly through the filter) if the exception + * returned was a NoSuchUniqueName. */ class HandleNoSuchUniqueNameCB implements Callback { public Object call(final Exception e) { if (e instanceof NoSuchUniqueName) { - - Deferred assignment = null; - synchronized (pending_assignments) { - assignment = pending_assignments.get(name); - if (assignment == null) { - // to prevent UID leaks that can be caused when multiple time - // series for the same metric or tags arrive, we need to write a - // deferred to the pending map as quickly as possible. Then we can - // start the assignment process after we've stashed the deferred - // and released the lock - assignment = new Deferred(); - pending_assignments.put(name, assignment); - } else { - LOG.info("Already waiting for UID assignment: " + name); - return assignment; - } + if (tsdb != null && tsdb.getUidFilter() != null && + tsdb.getUidFilter().fillterUIDAssignments()) { + return tsdb.getUidFilter() + .allowUIDAssignment(type, name, metric, tags) + .addCallbackDeferring(new AssignmentAllowedCB()); + } else { + return Deferred.fromResult(true) + .addCallbackDeferring(new AssignmentAllowedCB()); } - - // start the assignment dance after stashing the deferred - return new UniqueIdAllocator(name, assignment).tryAllocate(); } return e; // Other unexpected exception, let it bubble up. } diff --git a/src/uid/UniqueIdFilterPlugin.java b/src/uid/UniqueIdFilterPlugin.java new file mode 100644 index 0000000000..b0fd0c5e2a --- /dev/null +++ b/src/uid/UniqueIdFilterPlugin.java @@ -0,0 +1,101 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2016 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.uid; + +import java.util.Map; + +import com.stumbleupon.async.Deferred; + +import net.opentsdb.core.TSDB; +import net.opentsdb.stats.StatsCollector; +import net.opentsdb.uid.UniqueId.UniqueIdType; + +/** + * A filter that can determine whether or not UIDs should be allowed assignment + * based on their metric and tags. This is useful for such situations as: + *

  • Enforcing naming standards
  • + *
  • Blacklisting certain names or properties
  • + *
  • Preventing cardinality explosions
+ * Note: Implementations must have a parameterless constructor. The + * {@link #initialize(TSDB)} method will be called immediately after the plugin is + * instantiated and before any other methods are called. + * @since 2.3 + */ +public abstract class UniqueIdFilterPlugin { + + /** + * Called by TSDB to initialize the plugin + * Implementations are responsible for setting up any IO they need as well + * as starting any required background threads. + * Note: Implementations should throw exceptions if they can't start + * up properly. The TSD will then shutdown so the operator can fix the + * problem. Please use IllegalArgumentException for configuration issues. + * @param tsdb The parent TSDB object + * @throws IllegalArgumentException if required configuration parameters are + * missing + * @throws Exception if something else goes wrong + */ + public abstract void initialize(final TSDB tsdb); + + /** + * Called to gracefully shutdown the plugin. Implementations should close + * any IO they have open + * @return A deferred object that indicates the completion of the request. + * The {@link Object} has not special meaning and can be {@code null} + * (think of it as {@code Deferred}). + */ + public abstract Deferred shutdown(); + + /** + * Should return the version of this plugin in the format: + * MAJOR.MINOR.MAINT, e.g. "2.3.1". The MAJOR version should match the major + * version of OpenTSDB the plugin is meant to work with. + * @return A version string used to log the loaded version + */ + public abstract String version(); + + /** + * Called by the TSD when a request for statistics collection has come in. The + * implementation may provide one or more statistics. If no statistics are + * available for the implementation, simply stub the method. + * @param collector The collector used for emitting statistics + */ + public abstract void collectStats(final StatsCollector collector); + + /** + * Determine whether or not the UID should be assigned. + * If the UID should not be assigned a value, the implementation can return + * false or an exception in the deferred object. Otherwise it should return + * true and the UID will proceed with assignment. + * NOTE: In some cases the metric and tags may be null, particularly if the + * synchronous APIs were called. In such a situation, make sure the + * implementation handles it properly. + * @param type The type of UID being assigned + * @param value The string value of the UID + * @param metric The metric name associated with the UID for assignment + * @param tags The tag set associated with the UID + * @return True if the UID should be assigned, false if not. + */ + public abstract Deferred allowUIDAssignment( + final UniqueIdType type, + final String value, + final String metric, + final Map tags); + + /** + * Whether or not the filter should process UIDs. + * @return True if {@link #allowUIDAssignment(UniqueIdType, String, String, Map)} + * should be called, false if not. + */ + public abstract boolean fillterUIDAssignments(); +} diff --git a/src/utils/Config.java b/src/utils/Config.java index 5a61be5359..96be913863 100644 --- a/src/utils/Config.java +++ b/src/utils/Config.java @@ -572,6 +572,8 @@ protected void setDefaults() { default_map.put("tsd.storage.compaction.min_flush_threshold", "100"); default_map.put("tsd.storage.compaction.max_concurrent_flushes", "10000"); default_map.put("tsd.storage.compaction.flush_speed", "2"); + default_map.put("tsd.timeseriesfilter.enable", "false"); + default_map.put("tsd.uidfilter.enable", "false"); default_map.put("tsd.core.stats_with_port", "false"); default_map.put("tsd.http.show_stack_trace", "true"); default_map.put("tsd.http.query.allow_delete", "false"); diff --git a/test/core/BaseTsdbTest.java b/test/core/BaseTsdbTest.java index 2f83b61243..7c5f209abb 100644 --- a/test/core/BaseTsdbTest.java +++ b/test/core/BaseTsdbTest.java @@ -674,4 +674,16 @@ public boolean continuePausedTask() { } } } + + /** + * A little class used to throw a very specific type of exception for matching + * in Unit Tests. + */ + public static class UnitTestException extends RuntimeException { + public UnitTestException() { } + public UnitTestException(final String msg) { + super(msg); + } + private static final long serialVersionUID = -4404095849459619922L; + } } \ No newline at end of file diff --git a/test/core/TestTSDB.java b/test/core/TestTSDB.java index 91a97ec96e..64c4e64738 100644 --- a/test/core/TestTSDB.java +++ b/test/core/TestTSDB.java @@ -28,7 +28,6 @@ import net.opentsdb.utils.Config; import org.hbase.async.AtomicIncrementRequest; -import org.hbase.async.Bytes; import org.hbase.async.GetRequest; import org.hbase.async.HBaseClient; import org.hbase.async.KeyValue; @@ -37,11 +36,9 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; -import org.powermock.reflect.Whitebox; import com.stumbleupon.async.Deferred; @@ -453,632 +450,6 @@ public void uidTable() { assertArrayEquals("tsdb-uid".getBytes(), tsdb.uidTable()); } - @Test - public void addPointLong1Byte() throws Exception { - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 1356998400, 42, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, new byte[] { 0, 0 }); - assertNotNull(value); - assertEquals(42, value[0]); - } - - @Test - public void addPointLong1ByteNegative() throws Exception { - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 1356998400, -42, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, new byte[] { 0, 0 }); - assertNotNull(value); - assertEquals(-42, value[0]); - } - - @Test - public void addPointLong2Bytes() throws Exception { - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 1356998400, 257, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, new byte[] { 0, 1 }); - assertNotNull(value); - assertEquals(257, Bytes.getShort(value)); - } - - @Test - public void addPointLong2BytesNegative() throws Exception { - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 1356998400, -257, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, new byte[] { 0, 1 }); - assertNotNull(value); - assertEquals(-257, Bytes.getShort(value)); - } - - @Test - public void addPointLong4Bytes() throws Exception { - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 1356998400, 65537, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, new byte[] { 0, 3 }); - assertNotNull(value); - assertEquals(65537, Bytes.getInt(value)); - } - - @Test - public void addPointLong4BytesNegative() throws Exception { - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 1356998400, -65537, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, new byte[] { 0, 3 }); - assertNotNull(value); - assertEquals(-65537, Bytes.getInt(value)); - } - - @Test - public void addPointLong8Bytes() throws Exception { - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 1356998400, 4294967296L, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, new byte[] { 0, 7 }); - assertNotNull(value); - assertEquals(4294967296L, Bytes.getLong(value)); - } - - @Test - public void addPointLong8BytesNegative() throws Exception { - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 1356998400, -4294967296L, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, new byte[] { 0, 7 }); - assertNotNull(value); - assertEquals(-4294967296L, Bytes.getLong(value)); - } - - @Test - public void addPointLongMs() throws Exception { - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 1356998400500L, 42, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, - new byte[] { (byte) 0xF0, 0, 0x7D, 0 }); - assertNotNull(value); - assertEquals(42, value[0]); - } - - @Test - public void addPointLongMany() throws Exception { - setupAddPointStorage(); - - long timestamp = 1356998400; - for (int i = 1; i <= 50; i++) { - tsdb.addPoint(METRIC_STRING, timestamp++, i, tags).joinUninterruptibly(); - } - final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, new byte[] { 0, 0 }); - assertNotNull(value); - assertEquals(1, value[0]); - assertEquals(50, storage.numColumns(row)); - } - - @Test - public void addPointLongManyMs() throws Exception { - setupAddPointStorage(); - - long timestamp = 1356998400500L; - for (int i = 1; i <= 50; i++) { - tsdb.addPoint(METRIC_STRING, timestamp++, i, tags).joinUninterruptibly(); - } - final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, - new byte[] { (byte) 0xF0, 0, 0x7D, 0 }); - assertNotNull(value); - assertEquals(1, value[0]); - assertEquals(50, storage.numColumns(row)); - } - - @Test - public void addPointLongEndOfRow() throws Exception { - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 1357001999, 42, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, new byte[] { (byte) 0xE0, - (byte) 0xF0 }); - assertNotNull(value); - assertEquals(42, value[0]); - } - - @Test - public void addPointLongOverwrite() throws Exception { - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 1356998400, 42, tags).joinUninterruptibly(); - tsdb.addPoint(METRIC_STRING, 1356998400, 24, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, new byte[] { 0, 0 }); - assertNotNull(value); - assertEquals(24, value[0]); - } - - @Test (expected = NoSuchUniqueName.class) - public void addPointNoAutoMetric() throws Exception { - setupAddPointStorage(); - tsdb.addPoint(NSUN_METRIC, 1356998400, 42, tags).joinUninterruptibly(); - } - - @Test - public void addPointSecondZero() throws Exception { - // Thu, 01 Jan 1970 00:00:00 GMT - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 0, 42, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, new byte[] { 0, 0 }); - assertNotNull(value); - assertEquals(42, value[0]); - } - - @Test - public void addPointSecondOne() throws Exception { - // hey, it's valid *shrug* Thu, 01 Jan 1970 00:00:01 GMT - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 1, 42, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, new byte[] { 0, 16 }); - assertNotNull(value); - assertEquals(42, value[0]); - } - - @Test - public void addPointSecond2106() throws Exception { - // Sun, 07 Feb 2106 06:28:15 GMT - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 4294967295L, 42, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, (byte) 0xFF, (byte) 0xFF, (byte) 0xF9, - 0x60, 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, new byte[] { 0x69, (byte) 0xF0 }); - assertNotNull(value); - assertEquals(42, value[0]); - } - - @Test (expected = IllegalArgumentException.class) - public void addPointSecondNegative() throws Exception { - // Fri, 13 Dec 1901 20:45:52 GMT - // may support in the future, but 1.0 didn't - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, -2147483648, 42, tags).joinUninterruptibly(); - } - - @Test (expected = IllegalArgumentException.class) - public void emptyTagValue() throws Exception { - setupAddPointStorage(); - - tags.put(TAGK_STRING, ""); - tsdb.addPoint(METRIC_STRING, 1234567890, 42, tags).joinUninterruptibly(); - } - - @Test - public void addPointMS1970() throws Exception { - // Since it's just over Integer.MAX_VALUE, OpenTSDB will treat this as - // a millisecond timestamp since it doesn't fit in 4 bytes. - // Base time is 4294800 which is Thu, 19 Feb 1970 17:00:00 GMT - // offset = F0A36000 or 167296 ms - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 4294967296L, 42, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, 0, (byte) 0x41, (byte) 0x88, - (byte) 0x90, 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, new byte[] { (byte) 0xF0, - (byte) 0xA3, 0x60, 0}); - assertNotNull(value); - assertEquals(42, value[0]); - } - - @Test - public void addPointMS2106() throws Exception { - // Sun, 07 Feb 2106 06:28:15.000 GMT - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 4294967295000L, 42, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, (byte) 0xFF, (byte) 0xFF, (byte) 0xF9, - 0x60, 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, new byte[] { (byte) 0xF6, - (byte) 0x77, 0x46, 0 }); - assertNotNull(value); - assertEquals(42, value[0]); - } - - @Test - public void addPointMS2286() throws Exception { - // It's an artificial limit and more thought needs to be put into it - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 9999999999999L, 42, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, (byte) 0x54, (byte) 0x0B, (byte) 0xD9, - 0x10, 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, new byte[] { (byte) 0xFA, - (byte) 0xAE, 0x5F, (byte) 0xC0 }); - assertNotNull(value); - assertEquals(42, value[0]); - } - - @Test (expected = IllegalArgumentException.class) - public void addPointMSTooLarge() throws Exception { - // It's an artificial limit and more thought needs to be put into it - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 10000000000000L, 42, tags).joinUninterruptibly(); - } - - @Test (expected = IllegalArgumentException.class) - public void addPointMSNegative() throws Exception { - // Fri, 13 Dec 1901 20:45:52 GMT - // may support in the future, but 1.0 didn't - setupAddPointStorage(); - HashMap tags = new HashMap(1); - tags.put("host", "web01"); - tsdb.addPoint(METRIC_STRING, -2147483648000L, 42, tags).joinUninterruptibly(); - } - - @Test - public void addPointFloat() throws Exception { - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 1356998400, 42.5F, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, new byte[] { 0, 11 }); - assertNotNull(value); - // should have 7 digits of precision - assertEquals(42.5F, Float.intBitsToFloat(Bytes.getInt(value)), 0.0000001); - } - - @Test - public void addPointFloatNegative() throws Exception { - setupAddPointStorage(); - HashMap tags = new HashMap(1); - tags.put("host", "web01"); - tsdb.addPoint(METRIC_STRING, 1356998400, -42.5F, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, new byte[] { 0, 11 }); - assertNotNull(value); - // should have 7 digits of precision - assertEquals(-42.5F, Float.intBitsToFloat(Bytes.getInt(value)), 0.0000001); - } - - @Test - public void addPointFloatMs() throws Exception { - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 1356998400500L, 42.5F, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, - new byte[] { (byte) 0xF0, 0, 0x7D, 11 }); - assertNotNull(value); - // should have 7 digits of precision - assertEquals(42.5F, Float.intBitsToFloat(Bytes.getInt(value)), 0.0000001); - } - - @Test - public void addPointFloatEndOfRow() throws Exception { - setupAddPointStorage(); - HashMap tags = new HashMap(1); - tags.put("host", "web01"); - tsdb.addPoint(METRIC_STRING, 1357001999, 42.5F, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, new byte[] { (byte) 0xE0, - (byte) 0xFB }); - assertNotNull(value); - // should have 7 digits of precision - assertEquals(42.5F, Float.intBitsToFloat(Bytes.getInt(value)), 0.0000001); - } - - @Test - public void addPointFloatPrecision() throws Exception { - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 1356998400, 42.5123459999F, tags) - .joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, new byte[] { 0, 11 }); - assertNotNull(value); - // should have 7 digits of precision - assertEquals(42.512345F, Float.intBitsToFloat(Bytes.getInt(value)), 0.0000001); - } - - @Test - public void addPointFloatOverwrite() throws Exception { - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 1356998400, 42.5F, tags).joinUninterruptibly(); - tsdb.addPoint(METRIC_STRING, 1356998400, 25.4F, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, new byte[] { 0, 11 }); - assertNotNull(value); - // should have 7 digits of precision - assertEquals(25.4F, Float.intBitsToFloat(Bytes.getInt(value)), 0.0000001); - } - - @Test - public void addPointBothSameTimeIntAndFloat() throws Exception { - // this is an odd situation that can occur if the user puts an int and then - // a float (or vice-versa) with the same timestamp. What happens in the - // aggregators when this occurs? - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 1356998400, 42, tags).joinUninterruptibly(); - tsdb.addPoint(METRIC_STRING, 1356998400, 42.5F, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - byte[] value = storage.getColumn(row, new byte[] { 0, 0 }); - assertEquals(2, storage.numColumns(row)); - assertNotNull(value); - assertEquals(42, value[0]); - value = storage.getColumn(row, new byte[] { 0, 11 }); - assertNotNull(value); - // should have 7 digits of precision - assertEquals(42.5F, Float.intBitsToFloat(Bytes.getInt(value)), 0.0000001); - } - - @Test - public void addPointBothSameTimeIntAndFloatMs() throws Exception { - // this is an odd situation that can occur if the user puts an int and then - // a float (or vice-versa) with the same timestamp. What happens in the - // aggregators when this occurs? - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 1356998400500L, 42, tags).joinUninterruptibly(); - tsdb.addPoint(METRIC_STRING, 1356998400500L, 42.5F, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - byte[] value = storage.getColumn(row, new byte[] { (byte) 0xF0, 0, 0x7D, 0 }); - assertEquals(2, storage.numColumns(row)); - assertNotNull(value); - assertEquals(42, value[0]); - value = storage.getColumn(row, new byte[] { (byte) 0xF0, 0, 0x7D, 11 }); - assertNotNull(value); - // should have 7 digits of precision - assertEquals(42.5F, Float.intBitsToFloat(Bytes.getInt(value)), 0.0000001); - } - - @Test - public void addPointBothSameTimeSecondAndMs() throws Exception { - // this can happen if a second and an ms data point are stored for the same - // timestamp. - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 1356998400L, 42, tags).joinUninterruptibly(); - tsdb.addPoint(METRIC_STRING, 1356998400000L, 42, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - byte[] value = storage.getColumn(row, new byte[] { 0, 0 }); - assertEquals(2, storage.numColumns(row)); - assertNotNull(value); - assertEquals(42, value[0]); - value = storage.getColumn(row, new byte[] { (byte) 0xF0, 0, 0, 0 }); - assertNotNull(value); - // should have 7 digits of precision - assertEquals(42, value[0]); - } - - @Test - public void addPointWithSalt() throws Exception { - PowerMockito.mockStatic(Const.class); - PowerMockito.when(Const.SALT_WIDTH()).thenReturn(1); - PowerMockito.when(Const.SALT_BUCKETS()).thenReturn(20); - PowerMockito.when(Const.MAX_NUM_TAGS()).thenReturn((short) 8); - - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 1356998400, 42, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 8, 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, new byte[] { 0, 0 }); - assertNotNull(value); - assertEquals(42, value[0]); - } - - @Test - public void addPointWithSaltDifferentTags() throws Exception { - PowerMockito.mockStatic(Const.class); - PowerMockito.when(Const.SALT_WIDTH()).thenReturn(1); - PowerMockito.when(Const.SALT_BUCKETS()).thenReturn(20); - PowerMockito.when(Const.MAX_NUM_TAGS()).thenReturn((short) 8); - - setupAddPointStorage(); - tags.put(TAGK_STRING, TAGV_B_STRING); - - tsdb.addPoint(METRIC_STRING, 1356998400, 42, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 9, 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 2}; - final byte[] value = storage.getColumn(row, new byte[] { 0, 0 }); - assertNotNull(value); - assertEquals(42, value[0]); - } - - @Test - public void addPointWithSaltDifferentTime() throws Exception { - PowerMockito.mockStatic(Const.class); - PowerMockito.when(Const.SALT_WIDTH()).thenReturn(1); - PowerMockito.when(Const.SALT_BUCKETS()).thenReturn(20); - PowerMockito.when(Const.MAX_NUM_TAGS()).thenReturn((short) 8); - - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 1359680400, 42, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 8, 0, 0, 1, 0x51, (byte) 0x0B, 0x13, - (byte) 0x90, 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, new byte[] { 0, 0 }); - assertNotNull(value); - assertEquals(42, value[0]); - } - - @Test - public void addPointAppend() throws Exception { - Whitebox.setInternalState(config, "enable_appends", true); - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 1356998400, 42, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, - AppendDataPoints.APPEND_COLUMN_QUALIFIER); - assertArrayEquals(new byte[] { 0, 0, 42 }, value); - } - - @Test - public void addPointAppendWithOffset() throws Exception { - Whitebox.setInternalState(config, "enable_appends", true); - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 1356998430, 42, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, - AppendDataPoints.APPEND_COLUMN_QUALIFIER); - assertArrayEquals(new byte[] { 1, -32, 42 }, value); - } - - @Test - public void addPointAppendAppending() throws Exception { - Whitebox.setInternalState(config, "enable_appends", true); - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 1356998400, 42, tags).joinUninterruptibly(); - tsdb.addPoint(METRIC_STRING, 1356998430, 24, tags).joinUninterruptibly(); - tsdb.addPoint(METRIC_STRING, 1356998460, 1, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, - AppendDataPoints.APPEND_COLUMN_QUALIFIER); - assertArrayEquals(new byte[] { 0, 0, 42, 1, -32, 24, 3, -64, 1 }, value); - } - - @Test - public void addPointAppendAppendingOutOfOrder() throws Exception { - Whitebox.setInternalState(config, "enable_appends", true); - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 1356998400, 42, tags).joinUninterruptibly(); - tsdb.addPoint(METRIC_STRING, 1356998460, 1, tags).joinUninterruptibly(); - tsdb.addPoint(METRIC_STRING, 1356998430, 24, tags).joinUninterruptibly(); - - final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, - AppendDataPoints.APPEND_COLUMN_QUALIFIER); - assertArrayEquals(new byte[] { 0, 0, 42, 3, -64, 1, 1, -32, 24 }, value); - } - - @Test - public void addPointAppendAppendingDuplicates() throws Exception { - Whitebox.setInternalState(config, "enable_appends", true); - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 1356998400, 42, tags).joinUninterruptibly(); - tsdb.addPoint(METRIC_STRING, 1356998430, 24, tags).joinUninterruptibly(); - tsdb.addPoint(METRIC_STRING, 1356998430, 1, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, - AppendDataPoints.APPEND_COLUMN_QUALIFIER); - assertArrayEquals(new byte[] { 0, 0, 42, 1, -32, 24, 1, -32, 1 }, value); - } - - @Test - public void addPointAppendMS() throws Exception { - Whitebox.setInternalState(config, "enable_appends", true); - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 1356998400050L, 42, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, - AppendDataPoints.APPEND_COLUMN_QUALIFIER); - assertArrayEquals(new byte[] { (byte) 0xF0, 0, 12, -128, 42 }, value); - } - - @Test - public void addPointAppendAppendingMixMS() throws Exception { - Whitebox.setInternalState(config, "enable_appends", true); - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 1356998400, 42, tags).joinUninterruptibly(); - tsdb.addPoint(METRIC_STRING, 1356998400050L, 1, tags).joinUninterruptibly(); - tsdb.addPoint(METRIC_STRING, 1356998430, 24, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, - AppendDataPoints.APPEND_COLUMN_QUALIFIER); - assertArrayEquals(new byte[] { - 0, 0, 42, (byte) 0xF0, 0, 12, -128, 1, 1, -32, 24 }, value); - } - - @Test - public void addPointAppendWithSalt() throws Exception { - PowerMockito.mockStatic(Const.class); - PowerMockito.when(Const.SALT_WIDTH()).thenReturn(1); - PowerMockito.when(Const.SALT_BUCKETS()).thenReturn(20); - PowerMockito.when(Const.MAX_NUM_TAGS()).thenReturn((short) 8); - Whitebox.setInternalState(config, "enable_appends", true); - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 1356998400, 42, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 8, 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, - AppendDataPoints.APPEND_COLUMN_QUALIFIER); - assertArrayEquals(new byte[] { 0, 0, 42 }, value); - } - - @Test - public void addPointAppendAppendingWithSalt() throws Exception { - PowerMockito.mockStatic(Const.class); - PowerMockito.when(Const.SALT_WIDTH()).thenReturn(1); - PowerMockito.when(Const.SALT_BUCKETS()).thenReturn(20); - PowerMockito.when(Const.MAX_NUM_TAGS()).thenReturn((short) 8); - Whitebox.setInternalState(config, "enable_appends", true); - setupAddPointStorage(); - - tsdb.addPoint(METRIC_STRING, 1356998400, 42, tags).joinUninterruptibly(); - tsdb.addPoint(METRIC_STRING, 1356998430, 24, tags).joinUninterruptibly(); - tsdb.addPoint(METRIC_STRING, 1356998460, 1, tags).joinUninterruptibly(); - final byte[] row = new byte[] { 8, 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, - 0, 0, 1, 0, 0, 1}; - final byte[] value = storage.getColumn(row, - AppendDataPoints.APPEND_COLUMN_QUALIFIER); - assertArrayEquals(new byte[] { 0, 0, 42, 1, -32, 24, 3, -64, 1 }, value); - } - /** * Helper to mock the UID caches with valid responses */ diff --git a/test/core/TestTSDBAddPoint.java b/test/core/TestTSDBAddPoint.java new file mode 100644 index 0000000000..6e16f08622 --- /dev/null +++ b/test/core/TestTSDBAddPoint.java @@ -0,0 +1,690 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2016 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.core; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.anyShort; +import static org.mockito.Matchers.eq; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.HashMap; + +import org.hbase.async.Bytes; +import org.junit.Before; +import org.junit.Test; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.reflect.Whitebox; + +import com.stumbleupon.async.Deferred; + +import net.opentsdb.uid.NoSuchUniqueName; + +public class TestTSDBAddPoint extends BaseTsdbTest { + + @Before + public void beforeLocal() throws Exception { + setDataPointStorage(); + } + + @Test + public void addPointLong1Byte() throws Exception { + tsdb.addPoint(METRIC_STRING, 1356998400, 42, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, new byte[] { 0, 0 }); + assertNotNull(value); + assertEquals(42, value[0]); + } + + @Test + public void addPointLong1ByteNegative() throws Exception { + tsdb.addPoint(METRIC_STRING, 1356998400, -42, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, new byte[] { 0, 0 }); + assertNotNull(value); + assertEquals(-42, value[0]); + } + + @Test + public void addPointLong2Bytes() throws Exception { + tsdb.addPoint(METRIC_STRING, 1356998400, 257, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, new byte[] { 0, 1 }); + assertNotNull(value); + assertEquals(257, Bytes.getShort(value)); + } + + @Test + public void addPointLong2BytesNegative() throws Exception { + tsdb.addPoint(METRIC_STRING, 1356998400, -257, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, new byte[] { 0, 1 }); + assertNotNull(value); + assertEquals(-257, Bytes.getShort(value)); + } + + @Test + public void addPointLong4Bytes() throws Exception { + tsdb.addPoint(METRIC_STRING, 1356998400, 65537, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, new byte[] { 0, 3 }); + assertNotNull(value); + assertEquals(65537, Bytes.getInt(value)); + } + + @Test + public void addPointLong4BytesNegative() throws Exception { + tsdb.addPoint(METRIC_STRING, 1356998400, -65537, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, new byte[] { 0, 3 }); + assertNotNull(value); + assertEquals(-65537, Bytes.getInt(value)); + } + + @Test + public void addPointLong8Bytes() throws Exception { + tsdb.addPoint(METRIC_STRING, 1356998400, 4294967296L, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, new byte[] { 0, 7 }); + assertNotNull(value); + assertEquals(4294967296L, Bytes.getLong(value)); + } + + @Test + public void addPointLong8BytesNegative() throws Exception { + tsdb.addPoint(METRIC_STRING, 1356998400, -4294967296L, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, new byte[] { 0, 7 }); + assertNotNull(value); + assertEquals(-4294967296L, Bytes.getLong(value)); + } + + @Test + public void addPointLongMs() throws Exception { + tsdb.addPoint(METRIC_STRING, 1356998400500L, 42, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, + new byte[] { (byte) 0xF0, 0, 0x7D, 0 }); + assertNotNull(value); + assertEquals(42, value[0]); + } + + @Test + public void addPointLongMany() throws Exception { + long timestamp = 1356998400; + for (int i = 1; i <= 50; i++) { + tsdb.addPoint(METRIC_STRING, timestamp++, i, tags).joinUninterruptibly(); + } + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, new byte[] { 0, 0 }); + assertNotNull(value); + assertEquals(1, value[0]); + assertEquals(50, storage.numColumns(row)); + } + + @Test + public void addPointLongManyMs() throws Exception { + long timestamp = 1356998400500L; + for (int i = 1; i <= 50; i++) { + tsdb.addPoint(METRIC_STRING, timestamp++, i, tags).joinUninterruptibly(); + } + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, + new byte[] { (byte) 0xF0, 0, 0x7D, 0 }); + assertNotNull(value); + assertEquals(1, value[0]); + assertEquals(50, storage.numColumns(row)); + } + + @Test + public void addPointLongEndOfRow() throws Exception { + tsdb.addPoint(METRIC_STRING, 1357001999, 42, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, new byte[] { (byte) 0xE0, + (byte) 0xF0 }); + assertNotNull(value); + assertEquals(42, value[0]); + } + + @Test + public void addPointLongOverwrite() throws Exception { + tsdb.addPoint(METRIC_STRING, 1356998400, 42, tags).joinUninterruptibly(); + tsdb.addPoint(METRIC_STRING, 1356998400, 24, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, new byte[] { 0, 0 }); + assertNotNull(value); + assertEquals(24, value[0]); + } + + @Test (expected = NoSuchUniqueName.class) + public void addPointNoAutoMetric() throws Exception { + tsdb.addPoint(NSUN_METRIC, 1356998400, 42, tags).joinUninterruptibly(); + } + + @Test + public void addPointSecondZero() throws Exception { + // Thu, 01 Jan 1970 00:00:00 GMT + tsdb.addPoint(METRIC_STRING, 0, 42, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, new byte[] { 0, 0 }); + assertNotNull(value); + assertEquals(42, value[0]); + } + + @Test + public void addPointSecondOne() throws Exception { + // hey, it's valid *shrug* Thu, 01 Jan 1970 00:00:01 GMT + tsdb.addPoint(METRIC_STRING, 1, 42, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, new byte[] { 0, 16 }); + assertNotNull(value); + assertEquals(42, value[0]); + } + + @Test + public void addPointSecond2106() throws Exception { + // Sun, 07 Feb 2106 06:28:15 GMT + tsdb.addPoint(METRIC_STRING, 4294967295L, 42, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, (byte) 0xFF, (byte) 0xFF, (byte) 0xF9, + 0x60, 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, new byte[] { 0x69, (byte) 0xF0 }); + assertNotNull(value); + assertEquals(42, value[0]); + } + + @Test (expected = IllegalArgumentException.class) + public void addPointSecondNegative() throws Exception { + // Fri, 13 Dec 1901 20:45:52 GMT + // may support in the future, but 1.0 didn't + tsdb.addPoint(METRIC_STRING, -2147483648, 42, tags).joinUninterruptibly(); + } + + @Test (expected = IllegalArgumentException.class) + public void emptyTagValue() throws Exception { + tags.put(TAGK_STRING, ""); + tsdb.addPoint(METRIC_STRING, 1234567890, 42, tags).joinUninterruptibly(); + } + + @Test + public void addPointMS1970() throws Exception { + // Since it's just over Integer.MAX_VALUE, OpenTSDB will treat this as + // a millisecond timestamp since it doesn't fit in 4 bytes. + // Base time is 4294800 which is Thu, 19 Feb 1970 17:00:00 GMT + // offset = F0A36000 or 167296 ms + tsdb.addPoint(METRIC_STRING, 4294967296L, 42, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0, (byte) 0x41, (byte) 0x88, + (byte) 0x90, 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, new byte[] { (byte) 0xF0, + (byte) 0xA3, 0x60, 0}); + assertNotNull(value); + assertEquals(42, value[0]); + } + + @Test + public void addPointMS2106() throws Exception { + // Sun, 07 Feb 2106 06:28:15.000 GMT + tsdb.addPoint(METRIC_STRING, 4294967295000L, 42, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, (byte) 0xFF, (byte) 0xFF, (byte) 0xF9, + 0x60, 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, new byte[] { (byte) 0xF6, + (byte) 0x77, 0x46, 0 }); + assertNotNull(value); + assertEquals(42, value[0]); + } + + @Test + public void addPointMS2286() throws Exception { + // It's an artificial limit and more thought needs to be put into it + tsdb.addPoint(METRIC_STRING, 9999999999999L, 42, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, (byte) 0x54, (byte) 0x0B, (byte) 0xD9, + 0x10, 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, new byte[] { (byte) 0xFA, + (byte) 0xAE, 0x5F, (byte) 0xC0 }); + assertNotNull(value); + assertEquals(42, value[0]); + } + + @Test (expected = IllegalArgumentException.class) + public void addPointMSTooLarge() throws Exception { + // It's an artificial limit and more thought needs to be put into it + tsdb.addPoint(METRIC_STRING, 10000000000000L, 42, tags).joinUninterruptibly(); + } + + @Test (expected = IllegalArgumentException.class) + public void addPointMSNegative() throws Exception { + // Fri, 13 Dec 1901 20:45:52 GMT + // may support in the future, but 1.0 didn't + HashMap tags = new HashMap(1); + tags.put("host", "web01"); + tsdb.addPoint(METRIC_STRING, -2147483648000L, 42, tags).joinUninterruptibly(); + } + + @Test + public void addPointFloat() throws Exception { + tsdb.addPoint(METRIC_STRING, 1356998400, 42.5F, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, new byte[] { 0, 11 }); + assertNotNull(value); + // should have 7 digits of precision + assertEquals(42.5F, Float.intBitsToFloat(Bytes.getInt(value)), 0.0000001); + } + + @Test + public void addPointFloatNegative() throws Exception { + HashMap tags = new HashMap(1); + tags.put("host", "web01"); + tsdb.addPoint(METRIC_STRING, 1356998400, -42.5F, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, new byte[] { 0, 11 }); + assertNotNull(value); + // should have 7 digits of precision + assertEquals(-42.5F, Float.intBitsToFloat(Bytes.getInt(value)), 0.0000001); + } + + @Test + public void addPointFloatMs() throws Exception { + tsdb.addPoint(METRIC_STRING, 1356998400500L, 42.5F, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, + new byte[] { (byte) 0xF0, 0, 0x7D, 11 }); + assertNotNull(value); + // should have 7 digits of precision + assertEquals(42.5F, Float.intBitsToFloat(Bytes.getInt(value)), 0.0000001); + } + + @Test + public void addPointFloatEndOfRow() throws Exception { + HashMap tags = new HashMap(1); + tags.put("host", "web01"); + tsdb.addPoint(METRIC_STRING, 1357001999, 42.5F, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, new byte[] { (byte) 0xE0, + (byte) 0xFB }); + assertNotNull(value); + // should have 7 digits of precision + assertEquals(42.5F, Float.intBitsToFloat(Bytes.getInt(value)), 0.0000001); + } + + @Test + public void addPointFloatPrecision() throws Exception { + tsdb.addPoint(METRIC_STRING, 1356998400, 42.5123459999F, tags) + .joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, new byte[] { 0, 11 }); + assertNotNull(value); + // should have 7 digits of precision + assertEquals(42.512345F, Float.intBitsToFloat(Bytes.getInt(value)), 0.0000001); + } + + @Test + public void addPointFloatOverwrite() throws Exception { + tsdb.addPoint(METRIC_STRING, 1356998400, 42.5F, tags).joinUninterruptibly(); + tsdb.addPoint(METRIC_STRING, 1356998400, 25.4F, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, new byte[] { 0, 11 }); + assertNotNull(value); + // should have 7 digits of precision + assertEquals(25.4F, Float.intBitsToFloat(Bytes.getInt(value)), 0.0000001); + } + + @Test + public void addPointBothSameTimeIntAndFloat() throws Exception { + // this is an odd situation that can occur if the user puts an int and then + // a float (or vice-versa) with the same timestamp. What happens in the + // aggregators when this occurs? + tsdb.addPoint(METRIC_STRING, 1356998400, 42, tags).joinUninterruptibly(); + tsdb.addPoint(METRIC_STRING, 1356998400, 42.5F, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + byte[] value = storage.getColumn(row, new byte[] { 0, 0 }); + assertEquals(2, storage.numColumns(row)); + assertNotNull(value); + assertEquals(42, value[0]); + value = storage.getColumn(row, new byte[] { 0, 11 }); + assertNotNull(value); + // should have 7 digits of precision + assertEquals(42.5F, Float.intBitsToFloat(Bytes.getInt(value)), 0.0000001); + } + + @Test + public void addPointBothSameTimeIntAndFloatMs() throws Exception { + // this is an odd situation that can occur if the user puts an int and then + // a float (or vice-versa) with the same timestamp. What happens in the + // aggregators when this occurs? + tsdb.addPoint(METRIC_STRING, 1356998400500L, 42, tags).joinUninterruptibly(); + tsdb.addPoint(METRIC_STRING, 1356998400500L, 42.5F, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + byte[] value = storage.getColumn(row, new byte[] { (byte) 0xF0, 0, 0x7D, 0 }); + assertEquals(2, storage.numColumns(row)); + assertNotNull(value); + assertEquals(42, value[0]); + value = storage.getColumn(row, new byte[] { (byte) 0xF0, 0, 0x7D, 11 }); + assertNotNull(value); + // should have 7 digits of precision + assertEquals(42.5F, Float.intBitsToFloat(Bytes.getInt(value)), 0.0000001); + } + + @Test + public void addPointBothSameTimeSecondAndMs() throws Exception { + // this can happen if a second and an ms data point are stored for the same + // timestamp. + tsdb.addPoint(METRIC_STRING, 1356998400L, 42, tags).joinUninterruptibly(); + tsdb.addPoint(METRIC_STRING, 1356998400000L, 42, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + byte[] value = storage.getColumn(row, new byte[] { 0, 0 }); + assertEquals(2, storage.numColumns(row)); + assertNotNull(value); + assertEquals(42, value[0]); + value = storage.getColumn(row, new byte[] { (byte) 0xF0, 0, 0, 0 }); + assertNotNull(value); + // should have 7 digits of precision + assertEquals(42, value[0]); + } + + @Test + public void addPointWithSalt() throws Exception { + PowerMockito.mockStatic(Const.class); + PowerMockito.when(Const.SALT_WIDTH()).thenReturn(1); + PowerMockito.when(Const.SALT_BUCKETS()).thenReturn(20); + PowerMockito.when(Const.MAX_NUM_TAGS()).thenReturn((short) 8); + + tsdb.addPoint(METRIC_STRING, 1356998400, 42, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 8, 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, new byte[] { 0, 0 }); + assertNotNull(value); + assertEquals(42, value[0]); + } + + @Test + public void addPointWithSaltDifferentTags() throws Exception { + PowerMockito.mockStatic(Const.class); + PowerMockito.when(Const.SALT_WIDTH()).thenReturn(1); + PowerMockito.when(Const.SALT_BUCKETS()).thenReturn(20); + PowerMockito.when(Const.MAX_NUM_TAGS()).thenReturn((short) 8); + + tags.put(TAGK_STRING, TAGV_B_STRING); + + tsdb.addPoint(METRIC_STRING, 1356998400, 42, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 9, 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 2}; + final byte[] value = storage.getColumn(row, new byte[] { 0, 0 }); + assertNotNull(value); + assertEquals(42, value[0]); + } + + @Test + public void addPointWithSaltDifferentTime() throws Exception { + PowerMockito.mockStatic(Const.class); + PowerMockito.when(Const.SALT_WIDTH()).thenReturn(1); + PowerMockito.when(Const.SALT_BUCKETS()).thenReturn(20); + PowerMockito.when(Const.MAX_NUM_TAGS()).thenReturn((short) 8); + + tsdb.addPoint(METRIC_STRING, 1359680400, 42, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 8, 0, 0, 1, 0x51, (byte) 0x0B, 0x13, + (byte) 0x90, 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, new byte[] { 0, 0 }); + assertNotNull(value); + assertEquals(42, value[0]); + } + + @Test + public void addPointAppend() throws Exception { + Whitebox.setInternalState(config, "enable_appends", true); + + tsdb.addPoint(METRIC_STRING, 1356998400, 42, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, + AppendDataPoints.APPEND_COLUMN_QUALIFIER); + assertArrayEquals(new byte[] { 0, 0, 42 }, value); + } + + @Test + public void addPointAppendWithOffset() throws Exception { + Whitebox.setInternalState(config, "enable_appends", true); + + tsdb.addPoint(METRIC_STRING, 1356998430, 42, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, + AppendDataPoints.APPEND_COLUMN_QUALIFIER); + assertArrayEquals(new byte[] { 1, -32, 42 }, value); + } + + @Test + public void addPointAppendAppending() throws Exception { + Whitebox.setInternalState(config, "enable_appends", true); + + tsdb.addPoint(METRIC_STRING, 1356998400, 42, tags).joinUninterruptibly(); + tsdb.addPoint(METRIC_STRING, 1356998430, 24, tags).joinUninterruptibly(); + tsdb.addPoint(METRIC_STRING, 1356998460, 1, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, + AppendDataPoints.APPEND_COLUMN_QUALIFIER); + assertArrayEquals(new byte[] { 0, 0, 42, 1, -32, 24, 3, -64, 1 }, value); + } + + @Test + public void addPointAppendAppendingOutOfOrder() throws Exception { + Whitebox.setInternalState(config, "enable_appends", true); + + tsdb.addPoint(METRIC_STRING, 1356998400, 42, tags).joinUninterruptibly(); + tsdb.addPoint(METRIC_STRING, 1356998460, 1, tags).joinUninterruptibly(); + tsdb.addPoint(METRIC_STRING, 1356998430, 24, tags).joinUninterruptibly(); + + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, + AppendDataPoints.APPEND_COLUMN_QUALIFIER); + assertArrayEquals(new byte[] { 0, 0, 42, 3, -64, 1, 1, -32, 24 }, value); + } + + @Test + public void addPointAppendAppendingDuplicates() throws Exception { + Whitebox.setInternalState(config, "enable_appends", true); + + tsdb.addPoint(METRIC_STRING, 1356998400, 42, tags).joinUninterruptibly(); + tsdb.addPoint(METRIC_STRING, 1356998430, 24, tags).joinUninterruptibly(); + tsdb.addPoint(METRIC_STRING, 1356998430, 1, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, + AppendDataPoints.APPEND_COLUMN_QUALIFIER); + assertArrayEquals(new byte[] { 0, 0, 42, 1, -32, 24, 1, -32, 1 }, value); + } + + @Test + public void addPointAppendMS() throws Exception { + Whitebox.setInternalState(config, "enable_appends", true); + + tsdb.addPoint(METRIC_STRING, 1356998400050L, 42, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, + AppendDataPoints.APPEND_COLUMN_QUALIFIER); + assertArrayEquals(new byte[] { (byte) 0xF0, 0, 12, -128, 42 }, value); + } + + @Test + public void addPointAppendAppendingMixMS() throws Exception { + Whitebox.setInternalState(config, "enable_appends", true); + + tsdb.addPoint(METRIC_STRING, 1356998400, 42, tags).joinUninterruptibly(); + tsdb.addPoint(METRIC_STRING, 1356998400050L, 1, tags).joinUninterruptibly(); + tsdb.addPoint(METRIC_STRING, 1356998430, 24, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, + AppendDataPoints.APPEND_COLUMN_QUALIFIER); + assertArrayEquals(new byte[] { + 0, 0, 42, (byte) 0xF0, 0, 12, -128, 1, 1, -32, 24 }, value); + } + + @Test + public void addPointAppendWithSalt() throws Exception { + PowerMockito.mockStatic(Const.class); + PowerMockito.when(Const.SALT_WIDTH()).thenReturn(1); + PowerMockito.when(Const.SALT_BUCKETS()).thenReturn(20); + PowerMockito.when(Const.MAX_NUM_TAGS()).thenReturn((short) 8); + Whitebox.setInternalState(config, "enable_appends", true); + + tsdb.addPoint(METRIC_STRING, 1356998400, 42, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 8, 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, + AppendDataPoints.APPEND_COLUMN_QUALIFIER); + assertArrayEquals(new byte[] { 0, 0, 42 }, value); + } + + @Test + public void addPointAppendAppendingWithSalt() throws Exception { + PowerMockito.mockStatic(Const.class); + PowerMockito.when(Const.SALT_WIDTH()).thenReturn(1); + PowerMockito.when(Const.SALT_BUCKETS()).thenReturn(20); + PowerMockito.when(Const.MAX_NUM_TAGS()).thenReturn((short) 8); + Whitebox.setInternalState(config, "enable_appends", true); + + tsdb.addPoint(METRIC_STRING, 1356998400, 42, tags).joinUninterruptibly(); + tsdb.addPoint(METRIC_STRING, 1356998430, 24, tags).joinUninterruptibly(); + tsdb.addPoint(METRIC_STRING, 1356998460, 1, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 8, 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, + AppendDataPoints.APPEND_COLUMN_QUALIFIER); + assertArrayEquals(new byte[] { 0, 0, 42, 1, -32, 24, 3, -64, 1 }, value); + } + + @Test + public void dpFilterOK() throws Exception { + final WriteableDataPointFilterPlugin filter = + mock(WriteableDataPointFilterPlugin.class); + when(filter.filterDataPoints()).thenReturn(true); + when(filter.allowDataPoint(eq(METRIC_STRING), anyLong(), + any(byte[].class), eq(tags), anyShort())) + .thenReturn(Deferred.fromResult(true)); + Whitebox.setInternalState(tsdb, "ts_filter", filter); + + tsdb.addPoint(METRIC_STRING, 1356998400, 42, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, new byte[] { 0, 0 }); + assertNotNull(value); + assertEquals(42, value[0]); + + verify(filter, times(1)).filterDataPoints(); + verify(filter, times(1)).allowDataPoint(eq(METRIC_STRING), anyLong(), + any(byte[].class), eq(tags), anyShort()); + } + + @Test + public void dpFilterBlocked() throws Exception { + final WriteableDataPointFilterPlugin filter = + mock(WriteableDataPointFilterPlugin.class); + when(filter.filterDataPoints()).thenReturn(true); + when(filter.allowDataPoint(eq(METRIC_STRING), anyLong(), + any(byte[].class), eq(tags), anyShort())) + .thenReturn(Deferred.fromResult(false)); + Whitebox.setInternalState(tsdb, "ts_filter", filter); + + tsdb.addPoint(METRIC_STRING, 1356998400, 42, tags).joinUninterruptibly(); + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, new byte[] { 0, 0 }); + assertNull(value); + + verify(filter, times(1)).filterDataPoints(); + verify(filter, times(1)).allowDataPoint(eq(METRIC_STRING), anyLong(), + any(byte[].class), eq(tags), anyShort()); + } + + @Test + public void dpFilterReturnsException() throws Exception { + final WriteableDataPointFilterPlugin filter = + mock(WriteableDataPointFilterPlugin.class); + when(filter.filterDataPoints()).thenReturn(true); + when(filter.allowDataPoint(eq(METRIC_STRING), anyLong(), + any(byte[].class), eq(tags), anyShort())) + .thenReturn(Deferred.fromError(new UnitTestException("Boo!"))); + Whitebox.setInternalState(tsdb, "ts_filter", filter); + + final Deferred deferred = + tsdb.addPoint(METRIC_STRING, 1356998400, 42, tags); + try { + deferred.join(); + fail("Expected an UnitTestException"); + } catch (UnitTestException e) { }; + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, new byte[] { 0, 0 }); + assertNull(value); + + verify(filter, times(1)).filterDataPoints(); + verify(filter, times(1)).allowDataPoint(eq(METRIC_STRING), anyLong(), + any(byte[].class), eq(tags), anyShort()); + } + + @Test + public void uidFilterThrowsException() throws Exception { + final WriteableDataPointFilterPlugin filter = + mock(WriteableDataPointFilterPlugin.class); + when(filter.filterDataPoints()).thenReturn(true); + when(filter.allowDataPoint(eq(METRIC_STRING), anyLong(), + any(byte[].class), eq(tags), anyShort())) + .thenThrow(new UnitTestException("Boo!")); + Whitebox.setInternalState(tsdb, "ts_filter", filter); + + try { + tsdb.addPoint(METRIC_STRING, 1356998400, 42, tags); + fail("Expected an UnitTestException"); + } catch (UnitTestException e) { }; + final byte[] row = new byte[] { 0, 0, 1, 0x50, (byte) 0xE2, 0x27, 0, + 0, 0, 1, 0, 0, 1}; + final byte[] value = storage.getColumn(row, new byte[] { 0, 0 }); + assertNull(value); + + verify(filter, times(1)).filterDataPoints(); + verify(filter, times(1)).allowDataPoint(eq(METRIC_STRING), anyLong(), + any(byte[].class), eq(tags), anyShort()); + } +} diff --git a/test/core/TestTags.java b/test/core/TestTags.java index 3618c1ac7f..f6bea0e828 100644 --- a/test/core/TestTags.java +++ b/test/core/TestTags.java @@ -24,6 +24,7 @@ import net.opentsdb.query.filter.TagVRegexFilter; import net.opentsdb.query.filter.TagVWildcardFilter; import net.opentsdb.storage.MockBase; +import net.opentsdb.uid.FailedToAssignUniqueIdException; import net.opentsdb.uid.NoSuchUniqueId; import net.opentsdb.uid.NoSuchUniqueName; import net.opentsdb.uid.UniqueId; @@ -45,6 +46,7 @@ import org.powermock.modules.junit4.PowerMockRunner; import com.stumbleupon.async.Deferred; +import com.stumbleupon.async.DeferredGroupException; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -52,6 +54,9 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.anyMapOf; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.when; import static org.powermock.api.mockito.PowerMockito.mock; @@ -756,6 +761,32 @@ public void resolveOrCreateTagvNotAllowedBlocked() throws Exception { Tags.resolveOrCreateAll(tsdb, tags); } + @Test + public void resolveOrCreateAllAsync() throws Exception { + setupStorage(); + setupResolveAll(); + + final Map tags = new HashMap(1); + tags.put("host", "nohost"); + final List uids = Tags.resolveOrCreateAllAsync(tsdb, "metric", tags).join(); + assertEquals(1, uids.size()); + assertArrayEquals(new byte[] { 0, 0, 1, 0, 0, 3}, uids.get(0)); + } + + @Test (expected = DeferredGroupException.class) + public void resolveOrCreateAllAsyncFilterBlocked() throws Exception { + setupStorage(); + setupResolveAll(); + when(tag_names.getOrCreateIdAsync(eq("host"), anyString(), + anyMapOf(String.class, String.class))) + .thenReturn(Deferred.fromError(new FailedToAssignUniqueIdException( + "tagk", "host", 0, "Blocked by UID filter."))); + + final Map tags = new HashMap(1); + tags.put("host", "nohost"); + Tags.resolveOrCreateAllAsync(tsdb, "metric", tags).join(); + } + // PRIVATE helpers to setup unit tests private void setupStorage() throws Exception { @@ -799,16 +830,30 @@ private void setupResolveIds() throws Exception { } private void setupResolveAll() throws Exception { - when(tag_names.getOrCreateId("host")).thenReturn(new byte[] { 0, 0, 1 }); - when(tag_names.getOrCreateId("doesnotexist")) + when(tag_names.getOrCreateId(eq("host"))) + .thenReturn(new byte[] { 0, 0, 1 }); + when(tag_names.getOrCreateIdAsync(eq("host"), anyString(), + anyMapOf(String.class, String.class))) + .thenReturn(Deferred.fromResult(new byte[] { 0, 0, 1 })); + when(tag_names.getOrCreateId(eq("doesnotexist"))) .thenReturn(new byte[] { 0, 0, 3 }); + when(tag_names.getOrCreateIdAsync(eq("doesnotexist"), anyString(), + anyMapOf(String.class, String.class))) + .thenReturn(Deferred.fromResult(new byte[] { 0, 0, 3 })); when(tag_names.getId("pop")).thenReturn(new byte[] { 0, 0, 2 }); when(tag_names.getId("nonesuch")) .thenThrow(new NoSuchUniqueName("tagv", "nonesuch")); - when(tag_values.getOrCreateId("web01")).thenReturn(new byte[] { 0, 0, 1 }); - when(tag_values.getOrCreateId("nohost")) - .thenReturn(new byte[] { 0, 0, 3 }); + when(tag_values.getOrCreateId(eq("web01"))) + .thenReturn(new byte[] { 0, 0, 1 }); + when(tag_values.getOrCreateIdAsync(eq("web01"), anyString(), + anyMapOf(String.class, String.class))) + .thenReturn(Deferred.fromResult(new byte[] { 0, 0, 1 })); + when(tag_values.getOrCreateId(eq("nohost"))) + .thenReturn(new byte[] { 0, 0, 3 }); + when(tag_values.getOrCreateIdAsync(eq("nohost"), anyString(), + anyMapOf(String.class, String.class))) + .thenReturn(Deferred.fromResult(new byte[] { 0, 0, 3 })); when(tag_values.getId("web02")).thenReturn(new byte[] { 0, 0, 2 }); when(tag_values.getId("invalidhost")) .thenThrow(new NoSuchUniqueName("tagk", "invalidhost")); diff --git a/test/uid/TestUniqueId.java b/test/uid/TestUniqueId.java index d0990bdd77..4423b3dd65 100644 --- a/test/uid/TestUniqueId.java +++ b/test/uid/TestUniqueId.java @@ -22,7 +22,9 @@ import net.opentsdb.core.Const; import net.opentsdb.core.TSDB; +import net.opentsdb.core.BaseTsdbTest.UnitTestException; import net.opentsdb.storage.MockBase; +import net.opentsdb.uid.UniqueId.UniqueIdType; import net.opentsdb.utils.Config; import org.hbase.async.AtomicIncrementRequest; @@ -44,6 +46,8 @@ import static org.junit.Assert.*; import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyMapOf; +import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.argThat; import static org.mockito.Mockito.eq; @@ -372,6 +376,127 @@ public void getOrCreateIdAssignWhitelistedIdWithFailedWhitelist() { verify(client, times(2)).compareAndSet(anyPut(), emptyArray()); } + @Test + public void getOrCreateIdAsyncAssignFilterOK() throws Exception { + uid = new UniqueId(client, table, METRIC, 3); + final byte[] id = { 0, 0, 5 }; + final Config config = mock(Config.class); + when(config.enable_realtime_uid()).thenReturn(false); + final TSDB tsdb = mock(TSDB.class); + when(tsdb.getConfig()).thenReturn(config); + uid.setTSDB(tsdb); + final UniqueIdFilterPlugin filter = mock(UniqueIdFilterPlugin.class); + when(filter.fillterUIDAssignments()).thenReturn(true); + when(filter.allowUIDAssignment(any(UniqueIdType.class), anyString(), + anyString(), anyMapOf(String.class, String.class))) + .thenReturn(Deferred.fromResult(true)); + when(tsdb.getUidFilter()).thenReturn(filter); + when(client.get(anyGet())) // null => ID doesn't exist. + .thenReturn(Deferred.>fromResult(null)); + // Watch this! ______,^ I'm writing C++ in Java! + + when(client.atomicIncrement(incrementForRow(MAXID))) + .thenReturn(Deferred.fromResult(5L)); + + when(client.compareAndSet(anyPut(), emptyArray())) + .thenReturn(Deferred.fromResult(true)) + .thenReturn(Deferred.fromResult(true)); + + assertArrayEquals(id, uid.getOrCreateIdAsync("foo").join()); + // Should be a cache hit since we created that entry. + assertArrayEquals(id, uid.getOrCreateIdAsync("foo").join()); + // Should be a cache hit too for the same reason. + assertEquals("foo", uid.getName(id)); + + verify(client).get(anyGet()); // Initial Get. + verify(client).atomicIncrement(incrementForRow(MAXID)); + // Reverse + forward mappings. + verify(client, times(2)).compareAndSet(anyPut(), emptyArray()); + verify(filter, times(1)).allowUIDAssignment(any(UniqueIdType.class), anyString(), + anyString(), anyMapOf(String.class, String.class)); + } + + @Test (expected = FailedToAssignUniqueIdException.class) + public void getOrCreateIdAssignFilterBlocked() throws Exception { + uid = new UniqueId(client, table, METRIC, 3); + final Config config = mock(Config.class); + when(config.enable_realtime_uid()).thenReturn(false); + final TSDB tsdb = mock(TSDB.class); + when(tsdb.getConfig()).thenReturn(config); + uid.setTSDB(tsdb); + final UniqueIdFilterPlugin filter = mock(UniqueIdFilterPlugin.class); + when(filter.fillterUIDAssignments()).thenReturn(true); + when(filter.allowUIDAssignment(any(UniqueIdType.class), anyString(), + anyString(), anyMapOf(String.class, String.class))) + .thenReturn(Deferred.fromResult(false)); + when(tsdb.getUidFilter()).thenReturn(filter); + when(client.get(anyGet())) // null => ID doesn't exist. + .thenReturn(Deferred.>fromResult(null)); + + when(client.atomicIncrement(incrementForRow(MAXID))) + .thenReturn(Deferred.fromResult(5L)); + + when(client.compareAndSet(anyPut(), emptyArray())) + .thenReturn(Deferred.fromResult(true)) + .thenReturn(Deferred.fromResult(true)); + + uid.getOrCreateIdAsync("foo").join(); + } + + @Test (expected = UnitTestException.class) + public void getOrCreateIdAssignFilterReturnException() throws Exception { + uid = new UniqueId(client, table, METRIC, 3); + final Config config = mock(Config.class); + when(config.enable_realtime_uid()).thenReturn(false); + final TSDB tsdb = mock(TSDB.class); + when(tsdb.getConfig()).thenReturn(config); + uid.setTSDB(tsdb); + final UniqueIdFilterPlugin filter = mock(UniqueIdFilterPlugin.class); + when(filter.fillterUIDAssignments()).thenReturn(true); + when(filter.allowUIDAssignment(any(UniqueIdType.class), anyString(), + anyString(), anyMapOf(String.class, String.class))) + .thenReturn(Deferred.fromError(new UnitTestException())); + when(tsdb.getUidFilter()).thenReturn(filter); + when(client.get(anyGet())) // null => ID doesn't exist. + .thenReturn(Deferred.>fromResult(null)); + + when(client.atomicIncrement(incrementForRow(MAXID))) + .thenReturn(Deferred.fromResult(5L)); + + when(client.compareAndSet(anyPut(), emptyArray())) + .thenReturn(Deferred.fromResult(true)) + .thenReturn(Deferred.fromResult(true)); + + uid.getOrCreateIdAsync("foo").join(); + } + + @Test (expected = UnitTestException.class) + public void getOrCreateIdAssignFilterThrowsException() throws Exception { + uid = new UniqueId(client, table, METRIC, 3); + final Config config = mock(Config.class); + when(config.enable_realtime_uid()).thenReturn(false); + final TSDB tsdb = mock(TSDB.class); + when(tsdb.getConfig()).thenReturn(config); + uid.setTSDB(tsdb); + final UniqueIdFilterPlugin filter = mock(UniqueIdFilterPlugin.class); + when(filter.fillterUIDAssignments()).thenReturn(true); + when(filter.allowUIDAssignment(any(UniqueIdType.class), anyString(), + anyString(),anyMapOf(String.class, String.class))) + .thenThrow(new UnitTestException()); + when(tsdb.getUidFilter()).thenReturn(filter); + when(client.get(anyGet())) // null => ID doesn't exist. + .thenReturn(Deferred.>fromResult(null)); + + when(client.atomicIncrement(incrementForRow(MAXID))) + .thenReturn(Deferred.fromResult(5L)); + + when(client.compareAndSet(anyPut(), emptyArray())) + .thenReturn(Deferred.fromResult(true)) + .thenReturn(Deferred.fromResult(true)); + + uid.getOrCreateIdAsync("foo").join(); + } + @Test // Test the creation of an ID with no problem. public void checkMetricAgainstWhitelist() { setupWhitelists(METRIC); @@ -954,7 +1079,6 @@ public void getTagPairsFromTSUIDString() { assertArrayEquals(new byte[] { 0, 0, 3, 0, 0, 4 }, tags.get(1)); } - @Test public void getTagPairsFromTSUIDStringNonStandardWidth() { PowerMockito.mockStatic(TSDB.class); @@ -1010,7 +1134,6 @@ public void getTagPairsFromTSUIDBytes() { assertArrayEquals(new byte[] { 0, 0, 3, 0, 0, 4 }, tags.get(1)); } - @Test public void getTagPairsFromTSUIDBytesNonStandardWidth() { PowerMockito.mockStatic(TSDB.class); From 7b3db064a1179c6a73a56b145e4ac224754ba213 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Fri, 22 Apr 2016 20:57:31 -0700 Subject: [PATCH 183/233] Rollback 5747796c2a4398c43b332a165fbf39fb30f1d2a8 as we'll move the whitelist functionality into the new UniqueIdFilter class. Signed-off-by: Chris Larsen --- src/opentsdb.conf | 16 ---- src/tools/CliOptions.java | 11 --- src/uid/UniqueId.java | 69 +++++----------- src/utils/Config.java | 43 +--------- test/uid/TestUniqueId.java | 162 ++++++++++++++----------------------- 5 files changed, 80 insertions(+), 221 deletions(-) diff --git a/src/opentsdb.conf b/src/opentsdb.conf index 47c64c10a4..aa9cf9bffb 100644 --- a/src/opentsdb.conf +++ b/src/opentsdb.conf @@ -38,22 +38,6 @@ tsd.http.cachedir = # is False #tsd.core.auto_create_metrics = false -# Whether or no to evaluate new metric/tagk/tagv items against a whitelist, default -# is False -# tsd.core.auto_create_whitelist = false - -# Comma-Delimited list of regex patterns to match against new metric names, default -# is .*, examples might be ^awesome\..*$,^regexfoo[0-9].*$ -#tsd.core.auto_create_metrics_patterns = .* - -# Comma-Delimited list of regex patterns to match against new tagk names, default -# is .*, examples might be ^awesome\..*$,^regexfoo[0-9].*$ -#tsd.core.auto_create_tagk_patterns = .* - -# Comma-Delimited list of regex patterns to match against new tagv names, default -# is .*, examples might be ^awesome\..*$,^regexfoo[0-9].*$ -#tsd.core.auto_create_tagv_patterns = .* - # --------- STORAGE ---------- # Whether or not to enable data compaction in HBase, default is True #tsd.storage.enable_compaction = true diff --git a/src/tools/CliOptions.java b/src/tools/CliOptions.java index 2e55a7470d..377225ccef 100644 --- a/src/tools/CliOptions.java +++ b/src/tools/CliOptions.java @@ -120,17 +120,6 @@ static void overloadConfig(final ArgP argp, final Config config) { // map the overrides if (entry.getKey().toLowerCase().equals("--auto-metric")) { config.overrideConfig("tsd.core.auto_create_metrics", "true"); - } else if (entry.getKey().toLowerCase().equals("--auto-metric-whitelist")) { - config.overrideConfig("tsd.core.auto_create_whitelist", "true"); - } else if (entry.getKey().toLowerCase().equals("--auto-metric-pattern")) { - config.overrideConfig("tsd.core.auto_create_metrics_patterns", - entry.getValue()); - } else if (entry.getKey().toLowerCase().equals("--auto-tagk-pattern")) { - config.overrideConfig("tsd.core.auto_create_tagk_patterns", - entry.getValue()); - } else if (entry.getKey().toLowerCase().equals("--auto-tagv-pattern")) { - config.overrideConfig("tsd.core.auto_create_tagv_patterns", - entry.getValue()); } else if (entry.getKey().toLowerCase().equals("--table")) { config.overrideConfig("tsd.storage.hbase.data_table", entry.getValue()); } else if (entry.getKey().toLowerCase().equals("--uidtable")) { diff --git a/src/uid/UniqueId.java b/src/uid/UniqueId.java index 666190d007..9b218e2e00 100644 --- a/src/uid/UniqueId.java +++ b/src/uid/UniqueId.java @@ -23,7 +23,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.regex.Pattern; import javax.xml.bind.DatatypeConverter; @@ -237,10 +236,6 @@ public short width() { /** @param tsdb Whether or not to track new UIDMeta objects */ public void setTSDB(final TSDB tsdb) { this.tsdb = tsdb; - this.useWhitelist = tsdb.getConfig().auto_whitelist(); - this.auto_metric_patterns = tsdb.getConfig().auto_metric_patterns(); - this.auto_tagk_patterns = tsdb.getConfig().auto_tagk_patterns(); - this.auto_tagv_patterns = tsdb.getConfig().auto_tagv_patterns(); } /** The largest possible ID given the number of bytes the IDs are @@ -684,10 +679,25 @@ public byte[] getOrCreateId(final String name) throws HBaseException { try { return getIdAsync(name).joinUninterruptibly(); } catch (NoSuchUniqueName e) { - if (this.useWhitelist && !checkNameIsValid(name)) { - LOG.info("UID cannot be assigned, name is not acceptable because it fails to match the whitelist: " + name); - throw new RuntimeException("UID cannot be assigned, name is not acceptable because it fails to match the whitelist: " + name); + if (tsdb != null && tsdb.getUidFilter() != null && + tsdb.getUidFilter().fillterUIDAssignments()) { + try { + if (!tsdb.getUidFilter().allowUIDAssignment(type, name, null, null) + .join()) { + rejected_assignments++; + throw new FailedToAssignUniqueIdException(new String(kind), name, 0, + "Blocked by UID filter."); + } + } catch (FailedToAssignUniqueIdException e1) { + throw e1; + } catch (InterruptedException e1) { + LOG.error("Interrupted", e1); + Thread.currentThread().interrupt(); + } catch (Exception e1) { + throw new RuntimeException("Should never be here", e1); + } } + Deferred assignment = null; boolean pending = false; synchronized (pending_assignments) { @@ -734,48 +744,7 @@ public byte[] getOrCreateId(final String name) throws HBaseException { throw new RuntimeException("Should never be here", e); } } - - /** - * Checks to see if the provided string matches the acceptable - * patterns from the configuration. - *

- * - * @param name The name to compare to the acceptable name regexes - * @return - */ - public Boolean checkNameIsValid(final String name) throws RuntimeException { - final List rxs = new ArrayList(); - try { - String uid_patterns; - switch (type) { - case METRIC: uid_patterns = this.auto_metric_patterns; - break; - case TAGK: uid_patterns = this.auto_tagk_patterns; - break; - case TAGV: uid_patterns = this.auto_tagv_patterns; - break; - default: - throw new RuntimeException("Should never be here"); - } - String[] patterns = uid_patterns.split(","); - - for (String pattern : patterns) { - rxs.add(Pattern.compile(pattern)); - } - - for (Pattern rx : rxs) { - if (rx.matcher(name).matches()) { - LOG.debug("Accepted name for UID: " + name + " based on '" + rx.toString() + "'"); - return true; - } - } - LOG.debug("Rejected name for UID: " + name); - return false; - } catch (Exception e) { - throw new RuntimeException("Failed to check name (" + name + ") against patterns.", e); - } - } - + /** * Finds the ID associated with a given name or creates it. *

diff --git a/src/utils/Config.java b/src/utils/Config.java index 96be913863..a8e451723c 100644 --- a/src/utils/Config.java +++ b/src/utils/Config.java @@ -69,20 +69,7 @@ public class Config { /** tsd.core.auto_create_tagv */ private boolean auto_tagv = true; - - /** tsd.core.auto_create_whitelist */ - private boolean auto_whitelist = false; - - /** tsd.core.auto_create_metrics_patterns */ - private String auto_metric_patterns = ".*"; - - /** tsd.core.auto_create_tagk_patterns */ - private String auto_tagk_patterns = ".*"; - - /** tsd.core.auto_create_tagv_patterns */ - private String auto_tagv_patterns = ".*"; - - + /** tsd.storage.enable_compaction */ private boolean enable_compactions = true; @@ -196,25 +183,7 @@ public boolean auto_tagk() { public boolean auto_tagv() { return auto_tagv; } - - /** @return the auto_whitelist value */ - public boolean auto_whitelist() { return auto_whitelist; } - - /** @return the auto_metric value */ - public String auto_metric_patterns() { - return auto_metric_patterns; - } - - /** @return the auto_tagk value */ - public String auto_tagk_patterns() { - return auto_tagk_patterns; - } - - /** @return the auto_tagv value */ - public String auto_tagv_patterns() { - return auto_tagv_patterns; - } - + /** @param auto_metric whether or not to auto create metrics */ public void setAutoMetric(boolean auto_metric) { this.auto_metric = auto_metric; @@ -527,10 +496,6 @@ protected void setDefaults() { default_map.put("tsd.core.auto_create_metrics", "false"); default_map.put("tsd.core.auto_create_tagks", "true"); default_map.put("tsd.core.auto_create_tagvs", "true"); - default_map.put("tsd.core.auto_create_whitelist", "false"); - default_map.put("tsd.core.auto_create_metrics_patterns", ".*"); - default_map.put("tsd.core.auto_create_tagk_patterns", ".*"); - default_map.put("tsd.core.auto_create_tagv_patterns", ".*"); default_map.put("tsd.core.connections.limit", "0"); default_map.put("tsd.core.meta.enable_realtime_ts", "false"); default_map.put("tsd.core.meta.enable_realtime_uid", "false"); @@ -681,10 +646,6 @@ protected void loadStaticVariables() { auto_metric = this.getBoolean("tsd.core.auto_create_metrics"); auto_tagk = this.getBoolean("tsd.core.auto_create_tagks"); auto_tagv = this.getBoolean("tsd.core.auto_create_tagvs"); - auto_whitelist = this.getBoolean("tsd.core.auto_create_whitelist"); - auto_metric_patterns = this.getString("tsdb.core.auto_create_metrics_patterns"); - auto_tagk_patterns = this.getString("tsdb.core.auto_create_tagk_patterns"); - auto_tagv_patterns = this.getString("tsdb.core.auto_create_tagv_patterns"); enable_compactions = this.getBoolean("tsd.storage.enable_compaction"); enable_appends = this.getBoolean("tsd.storage.enable_appends"); repair_appends = this.getBoolean("tsd.storage.repair_appends"); diff --git a/test/uid/TestUniqueId.java b/test/uid/TestUniqueId.java index 4423b3dd65..0b0393f855 100644 --- a/test/uid/TestUniqueId.java +++ b/test/uid/TestUniqueId.java @@ -266,18 +266,20 @@ public void getOrCreateIdWithExistingId() { } @Test // Test the creation of an ID with no problem. - public void getOrCreateIdAssignIdWithSuccess() { + public void getOrCreateIdAssignFilterOK() { uid = new UniqueId(client, table, METRIC, 3); final byte[] id = { 0, 0, 5 }; final Config config = mock(Config.class); when(config.enable_realtime_uid()).thenReturn(false); - when(config.auto_whitelist()).thenReturn(false); - when(config.auto_metric_patterns()).thenReturn(".*"); - when(config.auto_tagk_patterns()).thenReturn(".*"); - when(config.auto_tagv_patterns()).thenReturn(".*"); final TSDB tsdb = mock(TSDB.class); when(tsdb.getConfig()).thenReturn(config); uid.setTSDB(tsdb); + final UniqueIdFilterPlugin filter = mock(UniqueIdFilterPlugin.class); + when(filter.fillterUIDAssignments()).thenReturn(true); + when(filter.allowUIDAssignment(any(UniqueIdType.class), anyString(), + anyString(), anyMapOf(String.class, String.class))) + .thenReturn(Deferred.fromResult(true)); + when(tsdb.getUidFilter()).thenReturn(filter); when(client.get(anyGet())) // null => ID doesn't exist. .thenReturn(Deferred.>fromResult(null)); @@ -300,22 +302,25 @@ public void getOrCreateIdAssignIdWithSuccess() { verify(client).atomicIncrement(incrementForRow(MAXID)); // Reverse + forward mappings. verify(client, times(2)).compareAndSet(anyPut(), emptyArray()); + verify(filter, times(1)).allowUIDAssignment(any(UniqueIdType.class), anyString(), + anyString(), anyMapOf(String.class, String.class)); } - @Test // Test the creation of an ID with no problem. - public void getOrCreateIdAssignWhitelistedIdWithSuccess() { + @Test (expected = FailedToAssignUniqueIdException.class) + public void getOrCreateIdAssignFilterBlocked() { uid = new UniqueId(client, table, METRIC, 3); - final byte[] id = { 0, 0, 5 }; final Config config = mock(Config.class); when(config.enable_realtime_uid()).thenReturn(false); - when(config.auto_whitelist()).thenReturn(true); - when(config.auto_metric_patterns()).thenReturn(".*"); - when(config.auto_tagk_patterns()).thenReturn(".*"); - when(config.auto_tagv_patterns()).thenReturn(".*"); final TSDB tsdb = mock(TSDB.class); when(tsdb.getConfig()).thenReturn(config); uid.setTSDB(tsdb); - + final UniqueIdFilterPlugin filter = mock(UniqueIdFilterPlugin.class); + when(filter.fillterUIDAssignments()).thenReturn(true); + when(filter.allowUIDAssignment(any(UniqueIdType.class), anyString(), + anyString(), anyMapOf(String.class, String.class))) + .thenReturn(Deferred.fromResult(false)); + when(tsdb.getUidFilter()).thenReturn(filter); + when(client.get(anyGet())) // null => ID doesn't exist. .thenReturn(Deferred.>fromResult(null)); // Watch this! ______,^ I'm writing C++ in Java! @@ -327,31 +332,23 @@ public void getOrCreateIdAssignWhitelistedIdWithSuccess() { .thenReturn(Deferred.fromResult(true)) .thenReturn(Deferred.fromResult(true)); - assertArrayEquals(id, uid.getOrCreateId("foo")); - // Should be a cache hit since we created that entry. - assertArrayEquals(id, uid.getOrCreateId("foo")); - // Should be a cache hit too for the same reason. - assertEquals("foo", uid.getName(id)); - - verify(client).get(anyGet()); // Initial Get. - verify(client).atomicIncrement(incrementForRow(MAXID)); - // Reverse + forward mappings. - verify(client, times(2)).compareAndSet(anyPut(), emptyArray()); + uid.getOrCreateId("foo"); } - @Test(expected=RuntimeException.class) - public void getOrCreateIdAssignWhitelistedIdWithFailedWhitelist() { + @Test(expected = RuntimeException.class) + public void getOrCreateIdAssignFilterReturnException() { uid = new UniqueId(client, table, METRIC, 3); - final byte[] id = { 0, 0, 5 }; final Config config = mock(Config.class); when(config.enable_realtime_uid()).thenReturn(false); - when(config.auto_whitelist()).thenReturn(true); - when(config.auto_metric_patterns()).thenReturn("^nomatch.*$"); - when(config.auto_tagk_patterns()).thenReturn("^sys\\.cpu\\.*$"); - when(config.auto_tagv_patterns()).thenReturn("^sys\\.cpu\\.*$"); final TSDB tsdb = mock(TSDB.class); when(tsdb.getConfig()).thenReturn(config); uid.setTSDB(tsdb); + final UniqueIdFilterPlugin filter = mock(UniqueIdFilterPlugin.class); + when(filter.fillterUIDAssignments()).thenReturn(true); + when(filter.allowUIDAssignment(any(UniqueIdType.class), anyString(), + anyString(), anyMapOf(String.class, String.class))) + .thenReturn(Deferred.fromError(new UnitTestException())); + when(tsdb.getUidFilter()).thenReturn(filter); when(client.get(anyGet())) // null => ID doesn't exist. .thenReturn(Deferred.>fromResult(null)); @@ -364,16 +361,36 @@ public void getOrCreateIdAssignWhitelistedIdWithFailedWhitelist() { .thenReturn(Deferred.fromResult(true)) .thenReturn(Deferred.fromResult(true)); - assertArrayEquals(id, uid.getOrCreateId("foo")); - // Should be a cache hit since we created that entry. - assertArrayEquals(id, uid.getOrCreateId("foo")); - // Should be a cache hit too for the same reason. - assertEquals("foo", uid.getName(id)); + uid.getOrCreateId("foo"); + } + + @Test(expected = RuntimeException.class) + public void getOrCreateIdAssignFilterThrowsException() { + uid = new UniqueId(client, table, METRIC, 3); + final Config config = mock(Config.class); + when(config.enable_realtime_uid()).thenReturn(false); + final TSDB tsdb = mock(TSDB.class); + when(tsdb.getConfig()).thenReturn(config); + uid.setTSDB(tsdb); + final UniqueIdFilterPlugin filter = mock(UniqueIdFilterPlugin.class); + when(filter.fillterUIDAssignments()).thenReturn(true); + when(filter.allowUIDAssignment(any(UniqueIdType.class), anyString(), + anyString(), anyMapOf(String.class, String.class))) + .thenThrow(new UnitTestException()); + when(tsdb.getUidFilter()).thenReturn(filter); - verify(client).get(anyGet()); // Initial Get. - verify(client).atomicIncrement(incrementForRow(MAXID)); - // Reverse + forward mappings. - verify(client, times(2)).compareAndSet(anyPut(), emptyArray()); + when(client.get(anyGet())) // null => ID doesn't exist. + .thenReturn(Deferred.>fromResult(null)); + // Watch this! ______,^ I'm writing C++ in Java! + + when(client.atomicIncrement(incrementForRow(MAXID))) + .thenReturn(Deferred.fromResult(5L)); + + when(client.compareAndSet(anyPut(), emptyArray())) + .thenReturn(Deferred.fromResult(true)) + .thenReturn(Deferred.fromResult(true)); + + uid.getOrCreateId("foo"); } @Test @@ -417,7 +434,7 @@ public void getOrCreateIdAsyncAssignFilterOK() throws Exception { } @Test (expected = FailedToAssignUniqueIdException.class) - public void getOrCreateIdAssignFilterBlocked() throws Exception { + public void getOrCreateIdAsyncAssignFilterBlocked() throws Exception { uid = new UniqueId(client, table, METRIC, 3); final Config config = mock(Config.class); when(config.enable_realtime_uid()).thenReturn(false); @@ -444,7 +461,7 @@ public void getOrCreateIdAssignFilterBlocked() throws Exception { } @Test (expected = UnitTestException.class) - public void getOrCreateIdAssignFilterReturnException() throws Exception { + public void getOrCreateIdAsyncAssignFilterReturnException() throws Exception { uid = new UniqueId(client, table, METRIC, 3); final Config config = mock(Config.class); when(config.enable_realtime_uid()).thenReturn(false); @@ -471,7 +488,7 @@ public void getOrCreateIdAssignFilterReturnException() throws Exception { } @Test (expected = UnitTestException.class) - public void getOrCreateIdAssignFilterThrowsException() throws Exception { + public void getOrCreateIdAsyncAssignFilterThrowsException() throws Exception { uid = new UniqueId(client, table, METRIC, 3); final Config config = mock(Config.class); when(config.enable_realtime_uid()).thenReturn(false); @@ -497,55 +514,6 @@ public void getOrCreateIdAssignFilterThrowsException() throws Exception { uid.getOrCreateIdAsync("foo").join(); } - @Test // Test the creation of an ID with no problem. - public void checkMetricAgainstWhitelist() { - setupWhitelists(METRIC); - assertTrue(uid.checkNameIsValid("sys.cpu.user")); - } - - @Test // Test the creation of an ID with no problem. - public void checkTagKAgainstWhitelist() { - setupWhitelists(TAGK); - assertTrue(uid.checkNameIsValid("sys.cpu.user")); - } - - @Test // Test the creation of an ID with no problem. - public void checkTagVAgainstWhitelist() { - setupWhitelists(TAGV); - assertTrue(uid.checkNameIsValid("sys.cpu.user")); - } - - @Test - public void checkMetricAgainstWhitelistFails() { - setupWhitelists(METRIC); - assertFalse(uid.checkNameIsValid("foo.badmetric")); - } - - @Test - public void checkTagKAgainstWhitelistFails() { - setupWhitelists(TAGK); - assertFalse(uid.checkNameIsValid("foo.badmetric")); - } - - @Test - public void checkTagVAgainstWhitelistFails() { - setupWhitelists(TAGV); - assertFalse(uid.checkNameIsValid("foo.badmetric")); - } - - private void setupWhitelists(String type) { - uid = new UniqueId(client, table, type, 3); - final Config config = mock(Config.class); - when(config.enable_realtime_uid()).thenReturn(false); - when(config.auto_whitelist()).thenReturn(true); - when(config.auto_metric_patterns()).thenReturn("sys.*"); - when(config.auto_tagk_patterns()).thenReturn("sys.*"); - when(config.auto_tagv_patterns()).thenReturn("sys.*"); - final TSDB tsdb = mock(TSDB.class); - when(tsdb.getConfig()).thenReturn(config); - uid.setTSDB(tsdb); - } - @Test // Test the creation of an ID when unable to increment MAXID public void getOrCreateIdUnableToIncrementMaxId() throws Exception { PowerMockito.mockStatic(Thread.class); @@ -676,10 +644,6 @@ public void getOrCreateIdWithICVFailure() { uid = new UniqueId(client, table, METRIC, 3); final Config config = mock(Config.class); when(config.enable_realtime_uid()).thenReturn(false); - when(config.auto_whitelist()).thenReturn(false); - when(config.auto_metric_patterns()).thenReturn(".*"); - when(config.auto_tagk_patterns()).thenReturn(".*"); - when(config.auto_tagv_patterns()).thenReturn(".*"); final TSDB tsdb = mock(TSDB.class); when(tsdb.getConfig()).thenReturn(config); uid.setTSDB(tsdb); @@ -712,10 +676,6 @@ public void getOrCreateIdPutsReverseMappingFirst() { uid = new UniqueId(client, table, METRIC, 3); final Config config = mock(Config.class); when(config.enable_realtime_uid()).thenReturn(false); - when(config.auto_whitelist()).thenReturn(false); - when(config.auto_metric_patterns()).thenReturn(".*"); - when(config.auto_tagk_patterns()).thenReturn(".*"); - when(config.auto_tagv_patterns()).thenReturn(".*"); final TSDB tsdb = mock(TSDB.class); when(tsdb.getConfig()).thenReturn(config); uid.setTSDB(tsdb); @@ -1511,10 +1471,6 @@ public void deleteNoSuchUniqueName() throws Exception { private void setupStorage() throws Exception { final Config config = mock(Config.class); - when(config.auto_whitelist()).thenReturn(false); - when(config.auto_metric_patterns()).thenReturn(".*"); - when(config.auto_tagk_patterns()).thenReturn(".*"); - when(config.auto_tagv_patterns()).thenReturn(".*"); when(tsdb.getConfig()).thenReturn(config); when(tsdb.getClient()).thenReturn(client); storage = new MockBase(tsdb, client, true, true, true, true); From d4fe65b7aeb40cc9e1e1533e458f2abd64f1ebb6 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sat, 23 Apr 2016 10:42:53 -0700 Subject: [PATCH 184/233] Clean up 107d9e9d04abafef970579530415e54665e9c46b a bit. We can use the StatsCollector class for the static settings and make it a a bit more flexible in case we need to add more stats in the future. Also remove the CLI options as we want folks to use configs instead of clogging up the command line. Signed-off-by: Chris Larsen --- Makefile.am | 4 +- src/core/TSDB.java | 3 ++ src/stats/StatsCollector.java | 33 +++++++++++--- src/tools/CliOptions.java | 6 +-- src/tools/TSDMain.java | 1 - src/tools/TSDPort.java | 56 ----------------------- src/utils/Config.java | 11 ----- test/tsd/TestStatsRpc.java | 29 ++++++++++++ test/tsd/TestStatsWithPort.java | 79 --------------------------------- 9 files changed, 62 insertions(+), 160 deletions(-) delete mode 100644 src/tools/TSDPort.java delete mode 100644 test/tsd/TestStatsWithPort.java diff --git a/Makefile.am b/Makefile.am index 02ebd0e50d..cfbd5e4bed 100644 --- a/Makefile.am +++ b/Makefile.am @@ -133,9 +133,8 @@ tsdb_SRC := \ src/tools/MetaPurge.java \ src/tools/MetaSync.java \ src/tools/Search.java \ - src/tools/StartupPlugin.java \ + src/tools/StartupPlugin.java \ src/tools/TSDMain.java \ - src/tools/TSDPort.java \ src/tools/TextImporter.java \ src/tools/TreeSync.java \ src/tools/UidManager.java \ @@ -347,7 +346,6 @@ test_SRC := \ test/tsd/TestSuggestRpc.java \ test/tsd/TestTreeRpc.java \ test/tsd/TestUniqueIdRpc.java \ - test/tsd/TestStatsWithPort.java \ test/uid/TestNoSuchUniqueId.java \ test/uid/TestRandomUniqueId.java \ test/uid/TestUniqueId.java \ diff --git a/src/core/TSDB.java b/src/core/TSDB.java index ba6839715c..4f20acb0dc 100644 --- a/src/core/TSDB.java +++ b/src/core/TSDB.java @@ -233,6 +233,9 @@ public TSDB(final HBaseClient client, final Config config) { // load up the functions that require the TSDB object ExpressionFactory.addTSDBFunctions(this); + // set any extra tags from the config for stats + StatsCollector.setGlobalTags(config); + LOG.debug(config.dumpConfiguration()); } diff --git a/src/stats/StatsCollector.java b/src/stats/StatsCollector.java index 4d9281f623..6170533fff 100644 --- a/src/stats/StatsCollector.java +++ b/src/stats/StatsCollector.java @@ -15,12 +15,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import net.opentsdb.tools.TSDPort; +import net.opentsdb.utils.Config; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.HashMap; import java.util.Map; +import java.util.Map.Entry; /** * Receives various stats/metrics from the current process. @@ -36,6 +37,9 @@ public abstract class StatsCollector { private static final Logger LOG = LoggerFactory.getLogger(StatsCollector.class); + /** Tags to add to every stat emitted by the collector */ + private static Map global_tags; + /** Prefix to add to every metric name, for example `tsd'. */ protected final String prefix; @@ -44,7 +48,7 @@ public abstract class StatsCollector { /** Buffer used to build lines emitted. */ private final StringBuilder buf = new StringBuilder(); - + /** * Constructor. * @param prefix A prefix to add to every metric name, for example @@ -52,11 +56,13 @@ public abstract class StatsCollector { */ public StatsCollector(final String prefix) { this.prefix = prefix; - if(TSDPort.isStatsWithPort()) { - addExtraTag("port", "" + TSDPort.getTSDPort()); + if (global_tags != null && !global_tags.isEmpty()) { + for (final Entry entry : global_tags.entrySet()) { + addExtraTag(entry.getKey(), entry.getValue()); + } } } - + /** * Method to override to actually emit a data point. * @param datapoint A data point in a format suitable for a text @@ -245,4 +251,21 @@ public final void clearExtraTag(final String name) { extratags.remove(name); } + /** + * Parses the configuration to determine if any extra tags should be included + * with every stat emitted. + * @param config The config object to parse + * @throws IllegalArgumentException if the config is null. Other exceptions + * may be thrown if the config values are unparseable. + */ + public static final void setGlobalTags(final Config config) { + if (config == null) { + throw new IllegalArgumentException("Configuration cannot be null."); + } + + if (config.getBoolean("tsd.core.stats_with_port")) { + global_tags = new HashMap(1); + global_tags.put("port", config.getString("tsd.network.port")); + } + } } diff --git a/src/tools/CliOptions.java b/src/tools/CliOptions.java index 377225ccef..cf5d58b4e0 100644 --- a/src/tools/CliOptions.java +++ b/src/tools/CliOptions.java @@ -148,11 +148,7 @@ static void overloadConfig(final ArgP argp, final Config config) { config.overrideConfig("tsd.network.async_io", entry.getValue()); } else if (entry.getKey().toLowerCase().equals("--worker-threads")) { config.overrideConfig("tsd.network.worker_threads", entry.getValue()); - } else if (entry.getKey().toLowerCase().equals("--max-connections")) { - config.overrideConfig("tsd.core.connections.limit", entry.getValue()); - } else if (entry.getKey().toLowerCase().equals("--statswport")) { - config.overrideConfig("tsd.core.stats_with_port", "true"); - } + } } } diff --git a/src/tools/TSDMain.java b/src/tools/TSDMain.java index 0c6f5d55d4..f26a895419 100644 --- a/src/tools/TSDMain.java +++ b/src/tools/TSDMain.java @@ -221,7 +221,6 @@ public static void main(String[] args) throws IOException { if (startup != null) { startup.setReady(tsdb); } - TSDPort.set(config); log.info("Ready to serve on " + addr); } catch (Throwable e) { factory.releaseExternalResources(); diff --git a/src/tools/TSDPort.java b/src/tools/TSDPort.java deleted file mode 100644 index 861a1c0d2e..0000000000 --- a/src/tools/TSDPort.java +++ /dev/null @@ -1,56 +0,0 @@ -// This file is part of OpenTSDB. -// Copyright (C) 2010-2012 The OpenTSDB Authors. -// -// This program is free software: you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 2.1 of the License, or (at your -// option) any later version. This program is distributed in the hope that it -// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty -// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser -// General Public License for more details. You should have received a copy -// of the GNU Lesser General Public License along with this program. If not, -// see . -package net.opentsdb.tools; - -import net.opentsdb.utils.Config; - -/** - * Static reference to the TSD's listening port and stats port configuration - */ - -public class TSDPort { - /** The RPC listening port */ - private static int rpcPort = -1; - /** Indicates if RPC stats include the listening port. Set by config tsd.core.stats_with_port - or CLI option --statswport. */ - private static boolean statsWithPort = false; - - /** - * Sets the rpc port and stats config on TSD startup - * @param config The final config - */ - static void set(Config config) { - rpcPort = config.getInt("tsd.network.port"); - statsWithPort = config.getBoolean("tsd.core.stats_with_port"); - } - - /** - * Returns the TSD's listening port - * @return the port - */ - public static int getTSDPort() { - return rpcPort; - } - - /** - * Indicates if stats should be reported with the port as a tag - * @return true if stats should be reported with the port as a tag, false otherwise - */ - public static boolean isStatsWithPort() { - return statsWithPort; - } - - - private TSDPort() {} - -} diff --git a/src/utils/Config.java b/src/utils/Config.java index a8e451723c..4927d43fbb 100644 --- a/src/utils/Config.java +++ b/src/utils/Config.java @@ -94,10 +94,6 @@ public class Config { /** tsd.http.request.enable_chunked */ private boolean enable_chunked_requests = false; - /** tsd.core.stats_with_port */ - private boolean stats_with_port = false; - - /** tsd.storage.fix_duplicates */ private boolean fix_duplicates = false; @@ -237,11 +233,6 @@ public boolean enable_chunked_requests() { return enable_chunked_requests; } - /** @return whether or not rpc stats should be broken out by port */ - public boolean rpc_stats_withport() { - return stats_with_port; - } - /** @return max incoming chunk size in bytes */ public int max_chunked_requests() { return max_chunked_requests; @@ -662,8 +653,6 @@ protected void loadStaticVariables() { enable_tree_processing = this.getBoolean("tsd.core.tree.enable_processing"); fix_duplicates = this.getBoolean("tsd.storage.fix_duplicates"); scanner_max_num_rows = this.getInt("tsd.storage.hbase.scanner.maxNumRows"); - stats_with_port = this.getBoolean("tsd.core.stats_with_port"); - } /** diff --git a/test/tsd/TestStatsRpc.java b/test/tsd/TestStatsRpc.java index fa90729245..2b09c73606 100644 --- a/test/tsd/TestStatsRpc.java +++ b/test/tsd/TestStatsRpc.java @@ -13,6 +13,7 @@ package net.opentsdb.tsd; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; @@ -21,6 +22,7 @@ import java.nio.charset.Charset; import net.opentsdb.core.TSDB; +import net.opentsdb.stats.StatsCollector; import net.opentsdb.utils.Config; import org.hbase.async.HBaseClient; @@ -45,6 +47,33 @@ public void before() throws Exception { when(tsdb.getClient()).thenReturn(client); } + @Test + public void statsWithOutPort() throws Exception { + final StatsRpc rpc = new StatsRpc(); + HttpQuery query = NettyMocks.getQuery(tsdb, "/api/stats"); + rpc.execute(tsdb, query); + assertEquals(HttpResponseStatus.OK, query.response().getStatus()); + final String json = + query.response().getContent().toString(Charset.forName("UTF-8")); + assertFalse(json.contains("port=4242")); + } + + @Test + public void statsWithPort() throws Exception { + when(tsdb.getConfig().getBoolean("tsd.core.stats_with_port")) + .thenReturn(true); + when(tsdb.getConfig().getString("tsd.network.port")) + .thenReturn("4242"); + StatsCollector.setGlobalTags(tsdb.getConfig()); + final StatsRpc rpc = new StatsRpc(); + HttpQuery query = NettyMocks.getQuery(tsdb, "/api/stats"); + rpc.execute(tsdb, query); + assertEquals(HttpResponseStatus.OK, query.response().getStatus()); + final String json = + query.response().getContent().toString(Charset.forName("UTF-8")); + assertTrue(json.contains("port=4242")); + } + @Test public void printThreadStats() throws Exception { final StatsRpc rpc = new StatsRpc(); diff --git a/test/tsd/TestStatsWithPort.java b/test/tsd/TestStatsWithPort.java deleted file mode 100644 index 85ca4001f1..0000000000 --- a/test/tsd/TestStatsWithPort.java +++ /dev/null @@ -1,79 +0,0 @@ -// This file is part of OpenTSDB. -// Copyright (C) 2015 The OpenTSDB Authors. -// -// This program is free software: you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 2.1 of the License, or (at your -// option) any later version. This program is distributed in the hope that it -// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty -// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser -// General Public License for more details. You should have received a copy -// of the GNU Lesser General Public License along with this program. If not, -// see . - -package net.opentsdb.tsd; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Pattern; - -import org.junit.Assert; -import org.junit.Test; - -import net.opentsdb.stats.StatsCollector; -import net.opentsdb.tools.TSDPort; - -public class TestStatsWithPort { - - static final Pattern PORT_MATCH = Pattern.compile(" port="); - - @Test - public void testNoPort() { - setPortConfig(4242, false); - } - - @Test - public void testDefaultPort() { - setPortConfig(4242, true); - } - - - protected void doTest() { - final List lines = new ArrayList(); - StatsCollector sc = new StatsCollector("tsd") { - @Override - public final void emit(final String line) { - lines.add(line); - } - }; - sc.record("foo", -1); - - } - - protected void validateStats(final List lines) { - Pattern portMatch = Pattern.compile(" port=" + TSDPort.getTSDPort()); - for(String s: lines) { - if(!TSDPort.isStatsWithPort()) { - Assert.assertFalse("Stat had a port", PORT_MATCH.matcher(s).find()); - } else { - Assert.assertTrue("Stat did not have port", portMatch.matcher(s).find()); - } - } - } - - - public void setPortConfig(final Integer port, final Boolean statsWithPort) { - try { - Field portField = TSDPort.class.getDeclaredField("rpcPort"); - portField.setAccessible(true); - portField.set(null, port); - Field statsWPortField = TSDPort.class.getDeclaredField("statsWithPort"); - statsWPortField.setAccessible(true); - statsWPortField.set(null, statsWithPort); - } catch (Exception ex) { - throw new RuntimeException("Failed to set TCPPort fields", ex); - } - } - -} From a4cf501bb2750b7de382c034cf981086a70309da Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sat, 23 Apr 2016 12:26:08 -0700 Subject: [PATCH 185/233] Add the UniqueIdWhitelistFilter plugin directly to the TSD code base so users can instantiate it without loading a separate file. Signed-off-by: Chris Larsen --- src/META-INF/MANIFEST.MF | 1 + .../net.opentsdb.uid.UniqueIdFilterPlugin | 1 + src/uid/UniqueIdWhitelistFilter.java | 200 ++++++++++++++++++ test/uid/TestUniqueIdWhitelistFilter.java | 165 +++++++++++++++ 4 files changed, 367 insertions(+) create mode 100644 src/META-INF/MANIFEST.MF create mode 100644 src/META-INF/services/net.opentsdb.uid.UniqueIdFilterPlugin create mode 100644 src/uid/UniqueIdWhitelistFilter.java create mode 100644 test/uid/TestUniqueIdWhitelistFilter.java diff --git a/src/META-INF/MANIFEST.MF b/src/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..348f1bdd38 --- /dev/null +++ b/src/META-INF/MANIFEST.MF @@ -0,0 +1 @@ +Manifest-Version: 1.0 \ No newline at end of file diff --git a/src/META-INF/services/net.opentsdb.uid.UniqueIdFilterPlugin b/src/META-INF/services/net.opentsdb.uid.UniqueIdFilterPlugin new file mode 100644 index 0000000000..010e95484c --- /dev/null +++ b/src/META-INF/services/net.opentsdb.uid.UniqueIdFilterPlugin @@ -0,0 +1 @@ +net.opentsdb.uid.UniqueIdWhitelistFilter \ No newline at end of file diff --git a/src/uid/UniqueIdWhitelistFilter.java b/src/uid/UniqueIdWhitelistFilter.java new file mode 100644 index 0000000000..371ea7bd50 --- /dev/null +++ b/src/uid/UniqueIdWhitelistFilter.java @@ -0,0 +1,200 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2016 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.uid; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import com.google.common.annotations.VisibleForTesting; +import com.stumbleupon.async.Deferred; + +import net.opentsdb.core.TSDB; +import net.opentsdb.stats.StatsCollector; +import net.opentsdb.uid.UniqueId.UniqueIdType; +import net.opentsdb.utils.Config; + +/** + * A UID filter implementation using regular expression based whitelists. + * Multiple regular expressions can be provided in the configuration file with + * a configurable delimiter. Each expression is compiled into a list per UID + * type and when a new UID passes through the filter, each expression in the + * list is compared to make sure the name satisfies all expressions. + */ +public class UniqueIdWhitelistFilter extends UniqueIdFilterPlugin { + + /** Default delimiter */ + public static final String DEFAULT_REGEX_DELIMITER = ","; + + /** Lists of patterns for each type. */ + private List metric_patterns; + private List tagk_patterns; + private List tagv_patterns; + + /** Counters for tracking stats */ + private final AtomicLong metrics_rejected = new AtomicLong(); + private final AtomicLong metrics_allowed = new AtomicLong(); + private final AtomicLong tagks_rejected = new AtomicLong(); + private final AtomicLong tagks_allowed = new AtomicLong(); + private final AtomicLong tagvs_rejected = new AtomicLong(); + private final AtomicLong tagvs_allowed = new AtomicLong(); + + @Override + public void initialize(final TSDB tsdb) { + final Config config = tsdb.getConfig(); + String delimiter = config.getString("tsd.uidfilter.whitelist.delimiter"); + if (delimiter == null) { + delimiter = DEFAULT_REGEX_DELIMITER; + } + + String raw = config.getString("tsd.uidfilter.whitelist.metric_patterns"); + if (raw != null) { + final String[] splits = raw.split(delimiter); + metric_patterns = new ArrayList(splits.length); + for (final String pattern : splits) { + try { + metric_patterns.add(Pattern.compile(pattern)); + } catch (PatternSyntaxException e) { + throw new IllegalArgumentException("The metric whitelist pattern [" + + pattern + "] does not compile.", e); + } + } + } + + raw = config.getString("tsd.uidfilter.whitelist.tagk_patterns"); + if (raw != null) { + final String[] splits = raw.split(delimiter); + tagk_patterns = new ArrayList(splits.length); + for (final String pattern : splits) { + try { + tagk_patterns.add(Pattern.compile(pattern)); + } catch (PatternSyntaxException e) { + throw new IllegalArgumentException("The tagk whitelist pattern [" + + pattern + "] does not compile.", e); + } + } + } + + raw = config.getString("tsd.uidfilter.whitelist.tagv_patterns"); + if (raw != null) { + final String[] splits = raw.split(delimiter); + tagv_patterns = new ArrayList(splits.length); + for (final String pattern : splits) { + try { + tagv_patterns.add(Pattern.compile(pattern)); + } catch (PatternSyntaxException e) { + throw new IllegalArgumentException("The tagv whitelist pattern [" + + pattern + "] does not compile.", e); + } + } + } + } + + @Override + public Deferred shutdown() { + return Deferred.fromResult(null); + } + + @Override + public String version() { + return "2.3.0"; + } + + @Override + public void collectStats(final StatsCollector collector) { + collector.record("uid.filter.whitelist.accepted", metrics_allowed.get(), + "type=metrics"); + collector.record("uid.filter.whitelist.accepted", tagks_allowed.get(), + "type=tagk"); + collector.record("uid.filter.whitelist.accepted", tagvs_allowed.get(), + "type=tagv"); + collector.record("uid.filter.whitelist.rejected", metrics_rejected.get(), + "type=metrics"); + collector.record("uid.filter.whitelist.rejected", tagks_rejected.get(), + "type=tagk"); + collector.record("uid.filter.whitelist.rejected", tagvs_rejected.get(), + "type=tagv"); + } + + @Override + public Deferred allowUIDAssignment( + final UniqueIdType type, + final String value, + final String metric, + final Map tags) { + + switch (type) { + case METRIC: + if (metric_patterns != null) { + for (final Pattern pattern : metric_patterns) { + if (!pattern.matcher(value).find()) { + metrics_rejected.incrementAndGet(); + return Deferred.fromResult(false); + } + } + } + metrics_allowed.incrementAndGet(); + break; + + case TAGK: + if (tagk_patterns != null) { + for (final Pattern pattern : tagk_patterns) { + if (!pattern.matcher(value).find()) { + tagks_rejected.incrementAndGet(); + return Deferred.fromResult(false); + } + } + } + tagks_allowed.incrementAndGet(); + break; + + case TAGV: + if (tagv_patterns != null) { + for (final Pattern pattern : tagv_patterns) { + if (!pattern.matcher(value).find()) { + tagvs_rejected.incrementAndGet(); + return Deferred.fromResult(false); + } + } + } + tagvs_allowed.incrementAndGet(); + break; + } + + // all patterns passed, yay! + return Deferred.fromResult(true); + } + + @Override + public boolean fillterUIDAssignments() { + return true; + } + + @VisibleForTesting + List metricPatterns() { + return metric_patterns; + } + + @VisibleForTesting + List tagkPatterns() { + return tagk_patterns; + } + + @VisibleForTesting + List tagvPatterns() { + return tagv_patterns; + } +} diff --git a/test/uid/TestUniqueIdWhitelistFilter.java b/test/uid/TestUniqueIdWhitelistFilter.java new file mode 100644 index 0000000000..c07f9c26e2 --- /dev/null +++ b/test/uid/TestUniqueIdWhitelistFilter.java @@ -0,0 +1,165 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2016 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.uid; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import net.opentsdb.core.TSDB; +import net.opentsdb.uid.UniqueId.UniqueIdType; +import net.opentsdb.utils.Config; + +@RunWith(PowerMockRunner.class) +//"Classloader hell"... It's real. Tell PowerMock to ignore these classes +//because they fiddle with the class loader. We don't test them anyway. +@PowerMockIgnore({"javax.management.*", "javax.xml.*", + "ch.qos.*", "org.slf4j.*", "com.sum.*", "org.xml.*"}) +@PrepareForTest({ TSDB.class, Config.class }) +public class TestUniqueIdWhitelistFilter { + + private TSDB tsdb; + private Config config; + private UniqueIdWhitelistFilter filter; + + @Before + public void before() throws Exception { + tsdb = PowerMockito.mock(TSDB.class); + config = new Config(false); + when(tsdb.getConfig()).thenReturn(config); + filter = new UniqueIdWhitelistFilter(); + + config.overrideConfig("tsd.uidfilter.whitelist.metric_patterns", ".*"); + config.overrideConfig("tsd.uidfilter.whitelist.tagk_patterns", ".*"); + config.overrideConfig("tsd.uidfilter.whitelist.tagv_patterns", ".*"); + } + + @Test + public void ctor() throws Exception { + assertNull(filter.metricPatterns()); + assertNull(filter.tagkPatterns()); + assertNull(filter.tagvPatterns()); + } + + @Test + public void initalize() throws Exception { + filter.initialize(tsdb); + assertEquals(1, filter.metricPatterns().size()); + assertEquals(".*", filter.metricPatterns().get(0).pattern()); + assertEquals(1, filter.tagkPatterns().size()); + assertEquals(".*", filter.tagkPatterns().get(0).pattern()); + assertEquals(1, filter.tagvPatterns().size()); + assertEquals(".*", filter.tagvPatterns().get(0).pattern()); + } + + @Test + public void initalizeMultiplePatterns() throws Exception { + config.overrideConfig("tsd.uidfilter.whitelist.metric_patterns", ".*,^test.*"); + config.overrideConfig("tsd.uidfilter.whitelist.tagk_patterns", ".*,^test.*"); + config.overrideConfig("tsd.uidfilter.whitelist.tagv_patterns", ".*,^test.*"); + filter.initialize(tsdb); + assertEquals(2, filter.metricPatterns().size()); + assertEquals(".*", filter.metricPatterns().get(0).pattern()); + assertEquals("^test.*", filter.metricPatterns().get(1).pattern()); + assertEquals(2, filter.tagkPatterns().size()); + assertEquals(".*", filter.tagkPatterns().get(0).pattern()); + assertEquals("^test.*", filter.tagkPatterns().get(1).pattern()); + assertEquals(2, filter.tagvPatterns().size()); + assertEquals(".*", filter.tagvPatterns().get(0).pattern()); + assertEquals("^test.*", filter.tagvPatterns().get(1).pattern()); + } + + @Test + public void initalizeMultiplePatternsAlternateDelimiter() throws Exception { + config.overrideConfig("tsd.uidfilter.whitelist.delimiter", "\\|"); + config.overrideConfig("tsd.uidfilter.whitelist.metric_patterns", ".*|^test.*"); + config.overrideConfig("tsd.uidfilter.whitelist.tagk_patterns", ".*|^test.*"); + config.overrideConfig("tsd.uidfilter.whitelist.tagv_patterns", ".*|^test.*"); + filter.initialize(tsdb); + assertEquals(2, filter.metricPatterns().size()); + assertEquals(".*", filter.metricPatterns().get(0).pattern()); + assertEquals("^test.*", filter.metricPatterns().get(1).pattern()); + assertEquals(2, filter.tagkPatterns().size()); + assertEquals(".*", filter.tagkPatterns().get(0).pattern()); + assertEquals("^test.*", filter.tagkPatterns().get(1).pattern()); + assertEquals(2, filter.tagvPatterns().size()); + assertEquals(".*", filter.tagvPatterns().get(0).pattern()); + assertEquals("^test.*", filter.tagvPatterns().get(1).pattern()); + } + + @Test (expected = IllegalArgumentException.class) + public void initalizeBadRegex() throws Exception { + config.overrideConfig("tsd.uidfilter.whitelist.metric_patterns", "grp[start"); + filter.initialize(tsdb); + } + + @Test + public void shutdown() throws Exception { + assertNull(filter.shutdown().join()); + } + + @Test + public void allowUIDAssignment() throws Exception { + config.overrideConfig("tsd.uidfilter.whitelist.metric_patterns", "^test.*"); + config.overrideConfig("tsd.uidfilter.whitelist.tagk_patterns", "^test.*"); + config.overrideConfig("tsd.uidfilter.whitelist.tagv_patterns", "^test.*"); + filter.initialize(tsdb); + assertTrue(filter.allowUIDAssignment(UniqueIdType.METRIC, "test_metric", + null, null).join()); + assertFalse(filter.allowUIDAssignment(UniqueIdType.METRIC, "metric", + null, null).join()); + assertTrue(filter.allowUIDAssignment(UniqueIdType.TAGK, "test_tagk", + null, null).join()); + assertFalse(filter.allowUIDAssignment(UniqueIdType.TAGK, "tagk", + null, null).join()); + assertTrue(filter.allowUIDAssignment(UniqueIdType.TAGV, "test_tagv", + null, null).join()); + assertFalse(filter.allowUIDAssignment(UniqueIdType.TAGV, "tagv", + null, null).join()); + } + + @Test + public void allowUIDAssignmentMultiplePaterns() throws Exception { + config.overrideConfig("tsd.uidfilter.whitelist.metric_patterns", ".*,^test.*"); + config.overrideConfig("tsd.uidfilter.whitelist.tagk_patterns", ".*,^test.*"); + config.overrideConfig("tsd.uidfilter.whitelist.tagv_patterns", ".*,^test.*"); + filter.initialize(tsdb); + assertTrue(filter.allowUIDAssignment(UniqueIdType.METRIC, "test_metric", + null, null).join()); + assertFalse(filter.allowUIDAssignment(UniqueIdType.METRIC, "metric", + null, null).join()); + assertTrue(filter.allowUIDAssignment(UniqueIdType.TAGK, "test_tagk", + null, null).join()); + assertFalse(filter.allowUIDAssignment(UniqueIdType.TAGK, "tagk", + null, null).join()); + assertTrue(filter.allowUIDAssignment(UniqueIdType.TAGV, "test_tagv", + null, null).join()); + assertFalse(filter.allowUIDAssignment(UniqueIdType.TAGV, "tagv", + null, null).join()); + } + + @Test + public void fillterUIDAssignments() throws Exception { + assertTrue(filter.fillterUIDAssignments()); + } +} From 971decfde75c1a2ec02e26fd287b4ae1790fecdf Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sat, 23 Apr 2016 13:07:03 -0700 Subject: [PATCH 186/233] Remove leftover defaults from the UniqueId class. Signed-off-by: Chris Larsen --- src/uid/UniqueId.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/uid/UniqueId.java b/src/uid/UniqueId.java index 9b218e2e00..22a384fc82 100644 --- a/src/uid/UniqueId.java +++ b/src/uid/UniqueId.java @@ -56,13 +56,6 @@ */ @SuppressWarnings("deprecation") // Dunno why even with this, compiler warns. public final class UniqueId implements UniqueIdInterface { - /** Whether or not to check new UID against configured whitelists **/ - private Boolean useWhitelist = false; - /** Whitelists for various uid types **/ - private String auto_metric_patterns = ".*"; - private String auto_tagk_patterns = ".*"; - private String auto_tagv_patterns = ".*"; - private static final Logger LOG = LoggerFactory.getLogger(UniqueId.class); /** Enumerator for different types of UIDS @since 2.0 */ From 866920fd1259295f02037d94bd39a0537361cdf4 Mon Sep 17 00:00:00 2001 From: Isaiah Choe Date: Thu, 15 Oct 2015 17:24:57 +0900 Subject: [PATCH 187/233] Allow to define special characters for keys and values of the tag via config tsd.core.tag.allow_specialchars Signed-off-by: Chris Larsen --- src/core/TSDB.java | 4 ++++ src/core/Tags.java | 19 ++++++++++++++++++- test/core/TestTags.java | 16 ++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/core/TSDB.java b/src/core/TSDB.java index 4f20acb0dc..57e591ef07 100644 --- a/src/core/TSDB.java +++ b/src/core/TSDB.java @@ -230,6 +230,10 @@ public TSDB(final HBaseClient client, final Config config) { UniqueId.preloadUidCache(this, uid_cache_map); } + if (config.getString("tsd.core.tag.allow_specialchars") != null) { + Tags.setAllowSpecialChars(config.getString("tsd.core.tag.allow_specialchars")); + } + // load up the functions that require the TSDB object ExpressionFactory.addTSDBFunctions(this); diff --git a/src/core/Tags.java b/src/core/Tags.java index 6aa5cc2074..ff1a4b2125 100644 --- a/src/core/Tags.java +++ b/src/core/Tags.java @@ -37,6 +37,7 @@ public final class Tags { private static final Logger LOG = LoggerFactory.getLogger(Tags.class); + private static String allowSpecialChars = ""; private Tags() { // Can't create instances of this utility class. @@ -547,7 +548,7 @@ public static void validateString(final String what, final String s) { final char c = s.charAt(i); if (!(('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '-' || c == '_' || c == '.' - || c == '/' || Character.isLetter(c))) { + || c == '/' || Character.isLetter(c) || isAllowSpecialChars(c))) { throw new IllegalArgumentException("Invalid " + what + " (\"" + s + "\"): illegal character: " + c); } @@ -808,4 +809,20 @@ public static boolean looksLikeInteger(final String value) { return true; } + /** + * Set the special characters due to allowing for a key or a value of the tag. + * @param characters character sequences as a string + */ + public static void setAllowSpecialChars(String characters) { + allowSpecialChars = characters == null ? "" : characters; + } + + /** + * Returns true if the character can be used a tag name or a tag value. + * @param character + * @return + */ + static boolean isAllowSpecialChars(char character) { + return allowSpecialChars.indexOf(character) != -1; + } } diff --git a/test/core/TestTags.java b/test/core/TestTags.java index f6bea0e828..2898807149 100644 --- a/test/core/TestTags.java +++ b/test/core/TestTags.java @@ -942,4 +942,20 @@ public void getTagUidsEmptyRow() throws Exception { final ByteMap uids = Tags.getTagUids(new byte[] {}); assertEquals(0, uids.size()); } + + @Test + public void setAllowSpecialChars() throws Exception { + assertFalse(Tags.isAllowSpecialChars('!')); + + Tags.setAllowSpecialChars(null); + assertFalse(Tags.isAllowSpecialChars('!')); + + Tags.setAllowSpecialChars(""); + assertFalse(Tags.isAllowSpecialChars('!')); + + Tags.setAllowSpecialChars("!)(%"); + assertTrue(Tags.isAllowSpecialChars('!')); + assertTrue(Tags.isAllowSpecialChars('(')); + assertTrue(Tags.isAllowSpecialChars('%')); + } } From 3d558060a0f4483f65348c8c9a78b9a31554159d Mon Sep 17 00:00:00 2001 From: HugoMFernandes Date: Fri, 15 Apr 2016 15:12:39 +0100 Subject: [PATCH 188/233] Fix #773 - Add global_annotations to graphs Fixed a bug in which global_annotations were ignored by GraphHandler.java (and thus, were not rendered by gnuplot). Added async call to hbase to retrieve global annotations when required in a similar fashion as that of QueryRpc.java. Note: Since both GraphHandler.java and QueryRpc.java implement (similar) query-processing logic, all of this should be unified in the future (requires a large refactor to split all query logic with visualization logic). Signed-off-by: Chris Larsen --- src/tsd/GraphHandler.java | 71 +++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 22 deletions(-) diff --git a/src/tsd/GraphHandler.java b/src/tsd/GraphHandler.java index d6ce433e13..3df3086a90 100644 --- a/src/tsd/GraphHandler.java +++ b/src/tsd/GraphHandler.java @@ -45,11 +45,15 @@ import net.opentsdb.core.TSDB; import net.opentsdb.core.TSQuery; import net.opentsdb.graph.Plot; +import net.opentsdb.meta.Annotation; import net.opentsdb.stats.Histogram; import net.opentsdb.stats.StatsCollector; import net.opentsdb.utils.DateTime; import net.opentsdb.utils.JSON; +import com.stumbleupon.async.Callback; +import com.stumbleupon.async.Deferred; + /** * Stateless handler of HTTP graph requests (the {@code /q} endpoint). */ @@ -123,6 +127,10 @@ public void execute(final TSDB tsdb, final HttpQuery query) { } } + // TODO(HugoMFernandes): Most of this (query-related) logic is implemented in + // net.opentsdb.tsd.QueryRpc.java (which actually does this asynchronously), + // so we should refactor both classes to split the actual logic used to + // generate the data from the actual visualization (removing all duped code). private void doGraph(final TSDB tsdb, final HttpQuery query) throws IOException { final String basepath = getGnuplotBasePath(tsdb, query); @@ -154,10 +162,15 @@ private void doGraph(final TSDB tsdb, final HttpQuery query) if (!nocache && isDiskCacheHit(query, end_time, max_age, basepath)) { return; } - Query[] tsdbqueries; - List options; - tsdbqueries = parseQuery(tsdb, query); - options = query.getQueryStringParams("o"); + + // Parse TSQuery from HTTP query + final TSQuery tsquery = QueryRpc.parseQuery(tsdb, query); + tsquery.validateAndSetQuery(); + + // Build the queries for the parsed TSQuery + Query[] tsdbqueries = tsquery.buildQueries(tsdb); + + List options = query.getQueryStringParams("o"); if (options == null) { options = new ArrayList(tsdbqueries.length); for (int i = 0; i < tsdbqueries.length; i++) { @@ -212,9 +225,37 @@ private void doGraph(final TSDB tsdb, final HttpQuery query) return; } + final RunGnuplot rungnuplot = new RunGnuplot(query, max_age, plot, basepath, + aggregated_tags, npoints); + + class ErrorCB implements Callback { + public Object call(final Exception e) throws Exception { + LOG.info("Failed to retrieve global annotations: ", e); + throw e; + } + } + + class GlobalCB implements Callback> { + public Object call(final List globalAnnotations) throws Exception { + rungnuplot.plot.setGlobals(globalAnnotations); + execGnuplot(rungnuplot, query); + + return null; + } + } + + // Fetch global annotations, if needed + if (!tsquery.getNoAnnotations() && tsquery.getGlobalAnnotations()) { + Annotation.getGlobalAnnotations(tsdb, start_time, end_time) + .addCallback(new GlobalCB()).addErrback(new ErrorCB()); + } else { + execGnuplot(rungnuplot, query); + } + } + + private void execGnuplot(RunGnuplot rungnuplot, HttpQuery query) { try { - gnuplot.execute(new RunGnuplot(query, max_age, plot, basepath, - aggregated_tags, npoints)); + gnuplot.execute(rungnuplot); } catch (RejectedExecutionException e) { query.internalError(new Exception("Too many requests pending," + " please try again later", e)); @@ -354,7 +395,7 @@ private String getGnuplotBasePath(final TSDB tsdb, final HttpQuery query) { qs.remove("png"); qs.remove("json"); qs.remove("ascii"); - return tsdb.getConfig().getDirectoryName("tsd.http.cachedir") + + return tsdb.getConfig().getDirectoryName("tsd.http.cachedir") + Integer.toHexString(qs.hashCode()); } @@ -841,21 +882,7 @@ private static void printMetricHeader(final PrintWriter writer, final String met writer.print(timestamp / 1000L); writer.print(' '); } - - /** - * Parses the {@code /q} query in a list of {@link Query} objects. - * @param tsdb The TSDB to use. - * @param query The HTTP query for {@code /q}. - * @return The corresponding {@link Query} objects. - * @throws BadRequestException if the query was malformed. - * @throws IllegalArgumentException if the metric or tags were malformed. - */ - private static Query[] parseQuery(final TSDB tsdb, final HttpQuery query) { - final TSQuery q = QueryRpc.parseQuery(tsdb, query, null); - q.validateAndSetQuery(); - return q.buildQueries(tsdb); - } - + private static final PlotThdFactory thread_factory = new PlotThdFactory(); private static final class PlotThdFactory implements ThreadFactory { From 95a67b2f3466a880ee5d05713378726540d38356 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sat, 23 Apr 2016 14:25:49 -0700 Subject: [PATCH 189/233] Add annotation flags to the UI to pass URI params properly for Signed-off-by: Chris Larsen --- src/tsd/GraphHandler.java | 6 +++--- src/tsd/client/QueryUi.java | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/tsd/GraphHandler.java b/src/tsd/GraphHandler.java index 3df3086a90..2669d6b8c6 100644 --- a/src/tsd/GraphHandler.java +++ b/src/tsd/GraphHandler.java @@ -230,14 +230,14 @@ private void doGraph(final TSDB tsdb, final HttpQuery query) class ErrorCB implements Callback { public Object call(final Exception e) throws Exception { - LOG.info("Failed to retrieve global annotations: ", e); + LOG.warn("Failed to retrieve global annotations: ", e); throw e; } } class GlobalCB implements Callback> { - public Object call(final List globalAnnotations) throws Exception { - rungnuplot.plot.setGlobals(globalAnnotations); + public Object call(final List global_annotations) throws Exception { + rungnuplot.plot.setGlobals(global_annotations); execGnuplot(rungnuplot, query); return null; diff --git a/src/tsd/client/QueryUi.java b/src/tsd/client/QueryUi.java index 3c063745de..23e4952c1c 100644 --- a/src/tsd/client/QueryUi.java +++ b/src/tsd/client/QueryUi.java @@ -149,6 +149,10 @@ public class QueryUi implements EntryPoint, HistoryListener { private final CheckBox smooth = new CheckBox(); private final ListBox styles = new ListBox(); private String timezone = ""; + + // Annotations handling flags. + private boolean hide_annotations = false; + private boolean show_global_annotations = false; /** * Handles every change to the query form and gets a new graph. @@ -798,6 +802,9 @@ private void refreshFromQueryString() { autoreload.setValue(qs.containsKey("autoreload"), true); maybeSetTextbox(qs, "autoreload", autoreoload_interval); + show_global_annotations = qs.containsKey("global_annotations") ? true : false; + hide_annotations = qs.containsKey("no_annotations") ? true : false; + //get the tz param value final ArrayList tzvalues = qs.get("tz"); if (tzvalues == null) @@ -944,6 +951,14 @@ private void refreshGraph() { url.append("&smooth=csplines"); } url.append("&style=").append(styles.getValue(styles.getSelectedIndex())); + + if (hide_annotations) { + url.append("&no_annotations=true"); + } + if (show_global_annotations) { + url.append("&global_annotations=true"); + } + final String unencodedUri = url.toString(); final String uri = URL.encode(unencodedUri); if (uri.equals(lastgraphuri)) { From cb836d9a5377b1ce2085eb521b745f982283b48e Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sat, 23 Apr 2016 14:35:03 -0700 Subject: [PATCH 190/233] Restore the 2.2 API for Query parsing. Signed-off-by: Chris Larsen --- src/tsd/QueryRpc.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/tsd/QueryRpc.java b/src/tsd/QueryRpc.java index 9a321025d7..2f6d92be24 100644 --- a/src/tsd/QueryRpc.java +++ b/src/tsd/QueryRpc.java @@ -494,6 +494,18 @@ public String toString() { } } + /** + * Parses a query string legacy style query from the URI + * @param tsdb The TSDB we belong to + * @param query The HTTP Query for parsing + * @return A TSQuery if parsing was successful + * @throws BadRequestException if parsing was unsuccessful + * @since 2.3 + */ + public static TSQuery parseQuery(final TSDB tsdb, final HttpQuery query) { + return parseQuery(tsdb, query, null); + } + /** * Parses a query string legacy style query from the URI * @param tsdb The TSDB we belong to @@ -502,6 +514,7 @@ public String toString() { * If this is null, it means any expressions in the URI will be skipped. * @return A TSQuery if parsing was successful * @throws BadRequestException if parsing was unsuccessful + * @since 2.3 */ public static TSQuery parseQuery(final TSDB tsdb, final HttpQuery query, final List expressions) { From 7d4f779b66bdfb1029e38460bcb6e5c522be4ba4 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sun, 24 Apr 2016 12:33:24 -0700 Subject: [PATCH 191/233] Add the "short-hostname" flag to travis to help the builds. Signed-off-by: Chris Larsen --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 1126638b1e..35b57e08a5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,8 @@ language: java before_script: ./build.sh pom.xml script: export MAVEN_OPTS="-Xmx1024m" && mvn test --quiet +addons: + hostname: short-hostname jdk: - oraclejdk7 - openjdk6 From 535b7e553812d1f2c13377d57e352e5c131a4097 Mon Sep 17 00:00:00 2001 From: Andy Flury Date: Wed, 9 Sep 2015 10:29:32 -0400 Subject: [PATCH 192/233] add first and last aggregator Signed-off-by: Chris Larsen --- src/core/Aggregators.java | 56 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/src/core/Aggregators.java b/src/core/Aggregators.java index 1d846301a5..ef1371bc68 100644 --- a/src/core/Aggregators.java +++ b/src/core/Aggregators.java @@ -81,7 +81,7 @@ public enum Interpolation { * if timestamps don't line up instead of interpolating. */ public static final Aggregator MIMMAX = new Max( Interpolation.MIN, "mimmax"); - + /** Aggregator that returns the number of data points. * WARNING: This currently interpolates with zero-if-missing. In this case * counts will be off when counting multiple time series. Only use this when @@ -89,6 +89,12 @@ public enum Interpolation { * @since 2.2 */ public static final Aggregator COUNT = new Count(Interpolation.ZIM, "count"); + /** Aggregator that returns the first data point. */ + public static final Aggregator FIRST = new First(Interpolation.ZIM, "first"); + + /** Aggregator that returns the first data point. */ + public static final Aggregator LAST = new Last(Interpolation.ZIM, "last"); + /** Maps an aggregator name to its instance. */ private static final HashMap aggregators; @@ -156,6 +162,8 @@ public enum Interpolation { aggregators.put("zimsum", ZIMSUM); aggregators.put("mimmin", MIMMIN); aggregators.put("mimmax", MIMMAX); + aggregators.put("first", FIRST); + aggregators.put("last", LAST); PercentileAgg[] percentiles = { p999, p99, p95, p90, p75, p50, @@ -643,7 +651,7 @@ public double runDouble(Doubles values) { return result / count; } - + class SumPoint { long ts; Object val; @@ -654,4 +662,48 @@ public SumPoint(long ts, Object val) { } } } + + private static final class First extends Aggregator { + public First(final Interpolation method, final String name) { + super(method, name); + } + + public long runLong(final Longs values) { + long val = values.nextLongValue(); + while (values.hasNextValue()) { + values.nextLongValue(); + } + return val; + } + + public double runDouble(final Doubles values) { + double val = values.nextDoubleValue(); + while (values.hasNextValue()) { + values.nextDoubleValue(); + } + return val; + } + } + + private static final class Last extends Aggregator { + public Last(final Interpolation method, final String name) { + super(method, name); + } + + public long runLong(final Longs values) { + long val = values.nextLongValue(); + while (values.hasNextValue()) { + val = values.nextLongValue(); + } + return val; + } + + public double runDouble(final Doubles values) { + double val = values.nextDoubleValue(); + while (values.hasNextValue()) { + val = values.nextDoubleValue(); + } + return val; + } + } } From 21f7e4606410a60802c8d639849183ff367bf8d1 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sun, 24 Apr 2016 12:48:08 -0700 Subject: [PATCH 193/233] Add unit tests for the first and last aggregators. Signed-off-by: Chris Larsen --- test/core/TestAggregators.java | 116 ++++++++++++++++++++++++--------- 1 file changed, 85 insertions(+), 31 deletions(-) diff --git a/test/core/TestAggregators.java b/test/core/TestAggregators.java index 5f51327046..7f4f843517 100644 --- a/test/core/TestAggregators.java +++ b/test/core/TestAggregators.java @@ -12,6 +12,8 @@ // see . package net.opentsdb.core; +import static org.junit.Assert.assertEquals; + import java.util.Random; import org.junit.Assert; @@ -36,26 +38,37 @@ public final class TestAggregators { /** Helper class to hold a bunch of numbers we can iterate on. */ private static final class Numbers implements Aggregator.Longs, Aggregator.Doubles { - private final long[] numbers; + private final long[] longs; + private final double[] doubles; private int i = 0; public Numbers(final long[] numbers) { - this.numbers = numbers; + longs = numbers; + doubles = null; + } + + public Numbers(final double[] numbers) { + longs = null; + doubles = numbers; } + public boolean isInteger() { + return longs != null ? true : false; + } + @Override public boolean hasNextValue() { - return i < numbers.length; + return longs != null ? i < longs.length : i < doubles.length; } @Override public long nextLongValue() { - return numbers[i++]; + return longs[i++]; } @Override public double nextDoubleValue() { - return numbers[i++]; + return doubles[i++]; } void reset() { @@ -112,9 +125,6 @@ private static void checkSimilarStdDev(final long[] values, final double epsilon) { final Numbers numbers = new Numbers(values); final Aggregator agg = Aggregators.get("dev"); - - Assert.assertEquals(expected, agg.runDouble(numbers), epsilon); - numbers.reset(); Assert.assertEquals(expected, agg.runLong(numbers), Math.max(epsilon, 1.0)); } @@ -141,32 +151,76 @@ public void testPercentiles() { } Numbers values = new Numbers(longValues); - assertEquals(500, Aggregators.get("p50"), values); - assertEquals(750, Aggregators.get("p75"), values); - assertEquals(900, Aggregators.get("p90"), values); - assertEquals(950, Aggregators.get("p95"), values); - assertEquals(990, Aggregators.get("p99"), values); - assertEquals(999, Aggregators.get("p999"), values); - - assertEquals(500, Aggregators.get("ep50r3"), values); - assertEquals(750, Aggregators.get("ep75r3"), values); - assertEquals(900, Aggregators.get("ep90r3"), values); - assertEquals(950, Aggregators.get("ep95r3"), values); - assertEquals(990, Aggregators.get("ep99r3"), values); - assertEquals(999, Aggregators.get("ep999r3"), values); + assertAggregatorEquals(500, Aggregators.get("p50"), values); + assertAggregatorEquals(750, Aggregators.get("p75"), values); + assertAggregatorEquals(900, Aggregators.get("p90"), values); + assertAggregatorEquals(950, Aggregators.get("p95"), values); + assertAggregatorEquals(990, Aggregators.get("p99"), values); + assertAggregatorEquals(999, Aggregators.get("p999"), values); + + assertAggregatorEquals(500, Aggregators.get("ep50r3"), values); + assertAggregatorEquals(750, Aggregators.get("ep75r3"), values); + assertAggregatorEquals(900, Aggregators.get("ep90r3"), values); + assertAggregatorEquals(950, Aggregators.get("ep95r3"), values); + assertAggregatorEquals(990, Aggregators.get("ep99r3"), values); + assertAggregatorEquals(999, Aggregators.get("ep999r3"), values); - assertEquals(500, Aggregators.get("ep50r7"), values); - assertEquals(750, Aggregators.get("ep75r7"), values); - assertEquals(900, Aggregators.get("ep90r7"), values); - assertEquals(950, Aggregators.get("ep95r7"), values); - assertEquals(990, Aggregators.get("ep99r7"), values); - assertEquals(999, Aggregators.get("ep999r7"), values); + assertAggregatorEquals(500, Aggregators.get("ep50r7"), values); + assertAggregatorEquals(750, Aggregators.get("ep75r7"), values); + assertAggregatorEquals(900, Aggregators.get("ep90r7"), values); + assertAggregatorEquals(950, Aggregators.get("ep95r7"), values); + assertAggregatorEquals(990, Aggregators.get("ep99r7"), values); + assertAggregatorEquals(999, Aggregators.get("ep999r7"), values); } - private void assertEquals(long value, Aggregator agg, Numbers numbers) { - Assert.assertEquals(value, agg.runLong(numbers)); - numbers.reset(); - Assert.assertEquals((double)value, agg.runDouble(numbers), 1.0); + @Test + public void testFirst() { + final long[] values = new long[10]; + for (int i = 0; i < values.length; i++) { + values[i] = i; + } + + Aggregator agg = Aggregators.FIRST; + Numbers numbers = new Numbers(values); + assertEquals(0, agg.runLong(numbers)); + + final double[] doubles = new double[10]; + double val = 0.5; + for (int i = 0; i < doubles.length; i++) { + doubles[i] = val++; + } + + numbers = new Numbers(doubles); + assertEquals(0.5, agg.runDouble(numbers), EPSILON_PERCENTAGE); + } + + @Test + public void testLast() { + final long[] values = new long[10]; + for (int i = 0; i < values.length; i++) { + values[i] = i; + } + + Aggregator agg = Aggregators.LAST; + Numbers numbers = new Numbers(values); + assertEquals(9, agg.runLong(numbers)); + + final double[] doubles = new double[10]; + double val = 0.5; + for (int i = 0; i < doubles.length; i++) { + doubles[i] = val++; + } + + numbers = new Numbers(doubles); + assertEquals(9.5, agg.runDouble(numbers), EPSILON_PERCENTAGE); + } + + private void assertAggregatorEquals(long value, Aggregator agg, Numbers numbers) { + if (numbers.isInteger()) { + Assert.assertEquals(value, agg.runLong(numbers)); + } else { + Assert.assertEquals((double)value, agg.runDouble(numbers), 1.0); + } numbers.reset(); } } From 2494721c8b1b3913645a36ccf826829d4191f45a Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Mon, 25 Apr 2016 12:05:27 -0700 Subject: [PATCH 194/233] Comment out the stats with/out port UTs for now. Signed-off-by: Chris Larsen --- test/tsd/TestStatsRpc.java | 55 ++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/test/tsd/TestStatsRpc.java b/test/tsd/TestStatsRpc.java index 2b09c73606..cad88aee7a 100644 --- a/test/tsd/TestStatsRpc.java +++ b/test/tsd/TestStatsRpc.java @@ -47,32 +47,35 @@ public void before() throws Exception { when(tsdb.getClient()).thenReturn(client); } - @Test - public void statsWithOutPort() throws Exception { - final StatsRpc rpc = new StatsRpc(); - HttpQuery query = NettyMocks.getQuery(tsdb, "/api/stats"); - rpc.execute(tsdb, query); - assertEquals(HttpResponseStatus.OK, query.response().getStatus()); - final String json = - query.response().getContent().toString(Charset.forName("UTF-8")); - assertFalse(json.contains("port=4242")); - } - - @Test - public void statsWithPort() throws Exception { - when(tsdb.getConfig().getBoolean("tsd.core.stats_with_port")) - .thenReturn(true); - when(tsdb.getConfig().getString("tsd.network.port")) - .thenReturn("4242"); - StatsCollector.setGlobalTags(tsdb.getConfig()); - final StatsRpc rpc = new StatsRpc(); - HttpQuery query = NettyMocks.getQuery(tsdb, "/api/stats"); - rpc.execute(tsdb, query); - assertEquals(HttpResponseStatus.OK, query.response().getStatus()); - final String json = - query.response().getContent().toString(Charset.forName("UTF-8")); - assertTrue(json.contains("port=4242")); - } +// TODO - revisit these as the one without port is failing intermittently. +// @Test +// public void statsWithOutPort() throws Exception { +// when(tsdb.getConfig().getBoolean("tsd.core.stats_with_port")) +// .thenReturn(false); +// final StatsRpc rpc = new StatsRpc(); +// HttpQuery query = NettyMocks.getQuery(tsdb, "/api/stats"); +// rpc.execute(tsdb, query); +// assertEquals(HttpResponseStatus.OK, query.response().getStatus()); +// final String json = +// query.response().getContent().toString(Charset.forName("UTF-8")); +// assertFalse(json.contains("port=4242")); +// } +// +// @Test +// public void statsWithPort() throws Exception { +// when(tsdb.getConfig().getBoolean("tsd.core.stats_with_port")) +// .thenReturn(true); +// when(tsdb.getConfig().getString("tsd.network.port")) +// .thenReturn("4242"); +// StatsCollector.setGlobalTags(tsdb.getConfig()); +// final StatsRpc rpc = new StatsRpc(); +// HttpQuery query = NettyMocks.getQuery(tsdb, "/api/stats"); +// rpc.execute(tsdb, query); +// assertEquals(HttpResponseStatus.OK, query.response().getStatus()); +// final String json = +// query.response().getContent().toString(Charset.forName("UTF-8")); +// assertTrue(json.contains("port=4242")); +// } @Test public void printThreadStats() throws Exception { From c13d32445a8d37e481ade5a64d66996d5ea193e0 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sun, 1 May 2016 10:59:35 -0700 Subject: [PATCH 195/233] Return more details when there's an expression error so users can debug it properly. Signed-off-by: Chris Larsen --- src/query/expression/ExpressionDataPoint.java | 11 +++++++++++ src/query/expression/ExpressionIterator.java | 15 +++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/query/expression/ExpressionDataPoint.java b/src/query/expression/ExpressionDataPoint.java index 35c7807af1..620a3d7f31 100644 --- a/src/query/expression/ExpressionDataPoint.java +++ b/src/query/expression/ExpressionDataPoint.java @@ -203,6 +203,17 @@ public void reset(final DataPoint dp) { this.dp.reset(dp); } + @Override + public String toString() { + final StringBuffer buf = new StringBuffer(); + buf.append("ExpressionDataPoint(metricUIDs=") + .append(metric_uids) + .append(", tsuids=") + .append(tsuids) + .append(")"); + return buf.toString(); + } + // DataPoint implementations @Override diff --git a/src/query/expression/ExpressionIterator.java b/src/query/expression/ExpressionIterator.java index 6684c60088..2e01585657 100644 --- a/src/query/expression/ExpressionIterator.java +++ b/src/query/expression/ExpressionIterator.java @@ -181,6 +181,16 @@ public String toString() { .append(id) .append(", expression=\"") .append(expression.toString()) + .append(", setOperator=") + .append(set_operator) + .append(", fillPolicy=") + .append(fill_policy) + .append(", intersectOnQueryTagks=") + .append(intersect_on_query_tagks) + .append(", includeAggTags=") + .append(include_agg_tags) + .append(", index=") + .append(index) .append("\", VariableIterator=") .append(iterator) .append(", dps=") @@ -220,12 +230,13 @@ public void compile() { LOG.debug("Compiling " + this); } if (results.size() < 1) { - throw new IllegalArgumentException("Missing query results."); + throw new IllegalArgumentException("No results for any variables in " + + "the expression: " + this); } if (results.size() < names.size()) { throw new IllegalArgumentException("Not enough query results [" + results.size() + "] for the expression variables [" - + names.size() + "]"); + + names.size() + "] " + this); } // don't care if we have extra results, but we had darned well better make From 28a99df37ebc4560808f784b480736a85e2083ea Mon Sep 17 00:00:00 2001 From: Kevin Bowling Date: Mon, 25 Apr 2016 00:06:37 -0700 Subject: [PATCH 196/233] Fix comment for behavior of tsd.network.tcp_no_delay Signed-off-by: Chris Larsen --- src/opentsdb.conf | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/opentsdb.conf b/src/opentsdb.conf index aa9cf9bffb..ba977a7b41 100644 --- a/src/opentsdb.conf +++ b/src/opentsdb.conf @@ -6,15 +6,14 @@ tsd.network.port = # The IPv4 network address to bind to, defaults to all addresses # tsd.network.bind = 0.0.0.0 -# Enables Nagel's algorithm to reduce the number of packets sent over the -# network, default is True +# Disable Nagel's algorithm, default is True #tsd.network.tcp_no_delay = true -# Determines whether or not to send keepalive packets to peers, default +# Determines whether or not to send keepalive packets to peers, default # is True #tsd.network.keep_alive = true -# Determines if the same socket should be used for new connections, default +# Determines if the same socket should be used for new connections, default # is True #tsd.network.reuse_address = true @@ -42,7 +41,7 @@ tsd.http.cachedir = # Whether or not to enable data compaction in HBase, default is True #tsd.storage.enable_compaction = true -# How often, in milliseconds, to flush the data point queue to storage, +# How often, in milliseconds, to flush the data point queue to storage, # default is 1,000 # tsd.storage.flush_interval = 1000 @@ -58,7 +57,7 @@ tsd.http.cachedir = # Path under which the znode for the -ROOT- region is located, default is "/hbase" #tsd.storage.hbase.zk_basedir = /hbase -# A comma separated list of Zookeeper hosts to connect to, with or without +# A comma separated list of Zookeeper hosts to connect to, with or without # port specifiers, default is "localhost" #tsd.storage.hbase.zk_quorum = localhost From 7a2f5825997db29a282f6236c0b18906acc7d885 Mon Sep 17 00:00:00 2001 From: Kevin Bowling Date: Mon, 25 Apr 2016 00:07:20 -0700 Subject: [PATCH 197/233] A few fixes for deb and rpm opentsdb.conf options Signed-off-by: Chris Larsen --- build-aux/deb/opentsdb.conf | 7 +++---- build-aux/rpm/opentsdb.conf | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/build-aux/deb/opentsdb.conf b/build-aux/deb/opentsdb.conf index 3d7db5bfa3..70afee8737 100644 --- a/build-aux/deb/opentsdb.conf +++ b/build-aux/deb/opentsdb.conf @@ -6,9 +6,8 @@ tsd.network.port = 4242 # The IPv4 network address to bind to, defaults to all addresses # tsd.network.bind = 0.0.0.0 -# Enables Nagel's algorithm to reduce the number of packets sent over the -# network, default is True -#tsd.network.tcpnodelay = true +# Disable Nagel's algorithm. Default is True +#tsd.network.tcp_no_delay = true # Determines whether or not to send keepalive packets to peers, default # is True @@ -16,7 +15,7 @@ tsd.network.port = 4242 # Determines if the same socket should be used for new connections, default # is True -#tsd.network.reuseaddress = true +#tsd.network.reuse_address = true # Number of worker threads dedicated to Netty, defaults to # of CPUs * 2 #tsd.network.worker_threads = 8 diff --git a/build-aux/rpm/opentsdb.conf b/build-aux/rpm/opentsdb.conf index caf4599acc..052936b962 100644 --- a/build-aux/rpm/opentsdb.conf +++ b/build-aux/rpm/opentsdb.conf @@ -6,9 +6,8 @@ tsd.network.port = 4242 # The IPv4 network address to bind to, defaults to all addresses # tsd.network.bind = 0.0.0.0 -# Enables Nagel's algorithm to reduce the number of packets sent over the -# network, default is True -#tsd.network.tcpnodelay = true +# Disable Nagel's algorithm, default is True +#tsd.network.tcp_no_delay = true # Determines whether or not to send keepalive packets to peers, default # is True @@ -16,7 +15,7 @@ tsd.network.port = 4242 # Determines if the same socket should be used for new connections, default # is True -#tsd.network.reuseaddress = true +#tsd.network.reuse_address = true # Number of worker threads dedicated to Netty, defaults to # of CPUs * 2 #tsd.network.worker_threads = 8 From 091a4c25d696f3f1b8b3ea5f750db2c77cb5ce4b Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sun, 1 May 2016 12:19:36 -0700 Subject: [PATCH 198/233] Fix issue #778 by allowing the creating of a TSMeta object without a TSUID. Also fix up some UTs in the UIDRPC class where the JSON order can change. Signed-off-by: Chris Larsen --- src/tsd/UniqueIdRpc.java | 9 ++- test/tsd/TestUniqueIdRpc.java | 142 ++++++++++++++++++++-------------- 2 files changed, 91 insertions(+), 60 deletions(-) diff --git a/src/tsd/UniqueIdRpc.java b/src/tsd/UniqueIdRpc.java index 340e6b4770..a9057866f8 100644 --- a/src/tsd/UniqueIdRpc.java +++ b/src/tsd/UniqueIdRpc.java @@ -553,8 +553,13 @@ private void handleRename(final TSDB tsdb, final HttpQuery query) { * be parsed */ private TSMeta parseTSMetaQS(final HttpQuery query) { - final String tsuid = query.getRequiredQueryStringParam("tsuid"); - final TSMeta meta = new TSMeta(tsuid); + final String tsuid = query.getQueryStringParam("tsuid"); + final TSMeta meta; + if (tsuid != null && !tsuid.isEmpty()) { + meta = new TSMeta(tsuid); + } else { + meta = new TSMeta(); + } final String display_name = query.getQueryStringParam("display_name"); if (display_name != null) { diff --git a/test/tsd/TestUniqueIdRpc.java b/test/tsd/TestUniqueIdRpc.java index 46b72e3a59..d3bf95de43 100644 --- a/test/tsd/TestUniqueIdRpc.java +++ b/test/tsd/TestUniqueIdRpc.java @@ -108,9 +108,10 @@ public void assignQsMetricDouble() throws Exception { "/api/uid/assign?metric=sys.cpu.0,sys.cpu.2"); this.rpc.execute(tsdb, query); assertEquals(HttpResponseStatus.OK, query.response().getStatus()); - assertEquals( - "{\"metric\":{\"sys.cpu.0\":\"000001\",\"sys.cpu.2\":\"000003\"}}", - query.response().getContent().toString(Charset.forName("UTF-8"))); + final String json = query.response().getContent() + .toString(Charset.forName("UTF-8")); + assertTrue(json.contains("\"sys.cpu.0\":\"000001\"")); + assertTrue(json.contains("\"sys.cpu.2\":\"000003\"")); } @Test @@ -120,9 +121,10 @@ public void assignQsMetricSingleBad() throws Exception { "/api/uid/assign?metric=sys.cpu.1"); this.rpc.execute(tsdb, query); assertEquals(HttpResponseStatus.BAD_REQUEST, query.response().getStatus()); - assertEquals("{\"metric_errors\":{\"sys.cpu.1\":\"Name already exists with " - + "UID: 000002\"},\"metric\":{}}", - query.response().getContent().toString(Charset.forName("UTF-8"))); + final String json = query.response().getContent() + .toString(Charset.forName("UTF-8")); + assertTrue(json.contains("{\"sys.cpu.1\":\"Name already exists with " + + "UID: 000002\"}")); } @Test @@ -132,10 +134,12 @@ public void assignQsMetric2Good1Bad() throws Exception { "/api/uid/assign?metric=sys.cpu.0,sys.cpu.1,sys.cpu.2"); this.rpc.execute(tsdb, query); assertEquals(HttpResponseStatus.BAD_REQUEST, query.response().getStatus()); - assertEquals("{\"metric_errors\":{\"sys.cpu.1\":\"Name already exists with " - + "UID: 000002\"},\"metric\":{\"sys.cpu.0\":\"000001\",\"sys.cpu.2\":" - + "\"000003\"}}", - query.response().getContent().toString(Charset.forName("UTF-8"))); + final String json = query.response().getContent() + .toString(Charset.forName("UTF-8")); + assertTrue(json.contains("{\"sys.cpu.1\":\"Name already exists with " + + "UID: 000002\"}")); + assertTrue(json.contains("{\"sys.cpu.0\":\"000001\",\"sys.cpu.2\":" + + "\"000003\"}")); } @Test @@ -156,9 +160,10 @@ public void assignQsTagkDouble() throws Exception { "/api/uid/assign?tagk=host,fqdn"); this.rpc.execute(tsdb, query); assertEquals(HttpResponseStatus.OK, query.response().getStatus()); - assertEquals( - "{\"tagk\":{\"fqdn\":\"000003\",\"host\":\"000001\"}}", - query.response().getContent().toString(Charset.forName("UTF-8"))); + final String json = query.response().getContent() + .toString(Charset.forName("UTF-8")); + assertTrue(json.contains("\"fqdn\":\"000003\"")); + assertTrue(json.contains("\"host\":\"000001\"")); } @Test @@ -168,9 +173,10 @@ public void assignQsTagkSingleBad() throws Exception { "/api/uid/assign?tagk=datacenter"); this.rpc.execute(tsdb, query); assertEquals(HttpResponseStatus.BAD_REQUEST, query.response().getStatus()); - assertEquals("{\"tagk_errors\":{\"datacenter\":\"Name already exists with " - + "UID: 000002\"},\"tagk\":{}}", - query.response().getContent().toString(Charset.forName("UTF-8"))); + final String json = query.response().getContent() + .toString(Charset.forName("UTF-8")); + assertTrue(json.contains("\"tagk_errors\":{\"datacenter\":" + + "\"Name already exists with UID: 000002\"}")); } @Test @@ -180,9 +186,12 @@ public void assignQsTagk2Good1Bad() throws Exception { "/api/uid/assign?tagk=host,datacenter,fqdn"); this.rpc.execute(tsdb, query); assertEquals(HttpResponseStatus.BAD_REQUEST, query.response().getStatus()); - assertEquals("{\"tagk_errors\":{\"datacenter\":\"Name already exists with " - + "UID: 000002\"},\"tagk\":{\"fqdn\":\"000003\",\"host\":\"000001\"}}", - query.response().getContent().toString(Charset.forName("UTF-8"))); + final String json = query.response().getContent() + .toString(Charset.forName("UTF-8")); + assertTrue(json.contains("{\"datacenter\":\"Name already exists with " + + "UID: 000002\"}")); + assertTrue(json.contains("\"fqdn\":\"000003\"")); + assertTrue(json.contains("\"host\":\"000001\"")); } @Test @@ -203,9 +212,10 @@ public void assignQsTagvDouble() throws Exception { "/api/uid/assign?tagv=localhost,foo"); this.rpc.execute(tsdb, query); assertEquals(HttpResponseStatus.OK, query.response().getStatus()); - assertEquals( - "{\"tagv\":{\"foo\":\"000003\",\"localhost\":\"000001\"}}", - query.response().getContent().toString(Charset.forName("UTF-8"))); + final String json = query.response().getContent() + .toString(Charset.forName("UTF-8")); + assertTrue(json.contains("\"foo\":\"000003\"")); + assertTrue(json.contains("\"localhost\":\"000001\"")); } @Test @@ -215,9 +225,10 @@ public void assignQsTagvSingleBad() throws Exception { "/api/uid/assign?tagv=myserver"); this.rpc.execute(tsdb, query); assertEquals(HttpResponseStatus.BAD_REQUEST, query.response().getStatus()); - assertEquals("{\"tagv\":{},\"tagv_errors\":{\"myserver\":\"Name already " - + "exists with UID: 000002\"}}", - query.response().getContent().toString(Charset.forName("UTF-8"))); + final String json = query.response().getContent() + .toString(Charset.forName("UTF-8")); + assertTrue(json.contains("\"tagv_errors\":{\"myserver\":\"Name already " + + "exists with UID: 000002\"}")); } @Test @@ -227,10 +238,12 @@ public void assignQsTagv2Good1Bad() throws Exception { "/api/uid/assign?tagv=localhost,myserver,foo"); this.rpc.execute(tsdb, query); assertEquals(HttpResponseStatus.BAD_REQUEST, query.response().getStatus()); - assertEquals("{\"tagv\":{\"foo\":\"000003\",\"localhost\":\"000001\"}," - + "\"tagv_errors\":{\"myserver\":\"Name already exists with " - + "UID: 000002\"}}", - query.response().getContent().toString(Charset.forName("UTF-8"))); + final String json = query.response().getContent() + .toString(Charset.forName("UTF-8")); + assertTrue(json.contains("\"foo\":\"000003\"")); + assertTrue(json.contains("\"localhost\":\"000001\"")); + assertTrue(json.contains("{\"myserver\":\"Name already exists with " + + "UID: 000002\"}")); } @Test @@ -298,9 +311,10 @@ public void assignPostMetricDouble() throws Exception { "{\"metric\":[\"sys.cpu.0\",\"sys.cpu.2\"]}"); this.rpc.execute(tsdb, query); assertEquals(HttpResponseStatus.OK, query.response().getStatus()); - assertEquals( - "{\"metric\":{\"sys.cpu.0\":\"000001\",\"sys.cpu.2\":\"000003\"}}", - query.response().getContent().toString(Charset.forName("UTF-8"))); + final String json = query.response().getContent() + .toString(Charset.forName("UTF-8")); + assertTrue(json.contains("\"sys.cpu.0\":\"000001\"")); + assertTrue(json.contains("\"sys.cpu.2\":\"000003\"")); } public void assignPostMetricSingleBad() throws Exception { @@ -309,9 +323,10 @@ public void assignPostMetricSingleBad() throws Exception { "{\"metric\":[\"sys.cpu.2\"]}"); this.rpc.execute(tsdb, query); assertEquals(HttpResponseStatus.OK, query.response().getStatus()); - assertEquals("{\"metric_errors\":{\"sys.cpu.1\":\"Name already exists with " - + "UID: 000002\"},\"metric\":{}}", - query.response().getContent().toString(Charset.forName("UTF-8"))); + final String json = query.response().getContent() + .toString(Charset.forName("UTF-8")); + assertTrue(json.contains("{\"sys.cpu.1\":\"Name already exists with " + + "UID: 000002\"}")); } public void assignPostMetric2Good1Bad() throws Exception { @@ -320,10 +335,12 @@ public void assignPostMetric2Good1Bad() throws Exception { "{\"metric\":[\"sys.cpu.0\",\"sys.cpu.1\",\"sys.cpu.2\"]}"); this.rpc.execute(tsdb, query); assertEquals(HttpResponseStatus.OK, query.response().getStatus()); - assertEquals("{\"metric_errors\":{\"sys.cpu.1\":\"Name already exists with " - + "UID: 000002\"},\"metric\":{\"sys.cpu.0\":\"000001\",\"sys.cpu.2\":" - + "\"000003\"}}", - query.response().getContent().toString(Charset.forName("UTF-8"))); + final String json = query.response().getContent() + .toString(Charset.forName("UTF-8")); + assertTrue(json.contains("{\"sys.cpu.1\":\"Name already exists with " + + "UID: 000002\"}")); + assertTrue(json.contains("\"sys.cpu.0\":\"000001\"")); + assertTrue(json.contains("\"sys.cpu.2\":\"000003\"")); } @Test @@ -343,9 +360,10 @@ public void assignPostTagkDouble() throws Exception { "{\"tagk\":[\"host\",\"fqdn\"]}"); this.rpc.execute(tsdb, query); assertEquals(HttpResponseStatus.OK, query.response().getStatus()); - assertEquals( - "{\"tagk\":{\"fqdn\":\"000003\",\"host\":\"000001\"}}", - query.response().getContent().toString(Charset.forName("UTF-8"))); + final String json = query.response().getContent() + .toString(Charset.forName("UTF-8")); + assertTrue(json.contains("\"fqdn\":\"000003\"")); + assertTrue(json.contains("\"host\":\"000001\"")); } public void assignPostTagkSingleBad() throws Exception { @@ -354,9 +372,10 @@ public void assignPostTagkSingleBad() throws Exception { "{\"tagk\":[\"datacenter\"]}"); this.rpc.execute(tsdb, query); assertEquals(HttpResponseStatus.OK, query.response().getStatus()); - assertEquals("{\"tagk_errors\":{\"datacenter\":\"Name already exists with " - + "UID: 000002\"},\"tagk\":{}}", - query.response().getContent().toString(Charset.forName("UTF-8"))); + final String json = query.response().getContent() + .toString(Charset.forName("UTF-8")); + assertTrue(json.contains("\"datacenter\":\"Name already exists with " + + "UID: 000002\"")); } public void assignPostTagk2Good1Bad() throws Exception { @@ -365,9 +384,12 @@ public void assignPostTagk2Good1Bad() throws Exception { "{\"tagk\":[\"host\",\"datacenter\",\"fqdn\"]}"); this.rpc.execute(tsdb, query); assertEquals(HttpResponseStatus.OK, query.response().getStatus()); - assertEquals("{\"tagk_errors\":{\"datacenter\":\"Name already exists with " - + "UID: 000002\"},\"tagk\":{\"fqdn\":\"000003\",\"host\":\"000001\"}}", - query.response().getContent().toString(Charset.forName("UTF-8"))); + final String json = query.response().getContent() + .toString(Charset.forName("UTF-8")); + assertTrue(json.contains("{\"datacenter\":\"Name already exists with " + + "UID: 000002\"}")); + assertTrue(json.contains("\"fqdn\":\"000003\"")); + assertTrue(json.contains("\"host\":\"000001\"")); } @Test @@ -387,9 +409,10 @@ public void assignPostTagvDouble() throws Exception { "{\"tagv\":[\"localhost\",\"foo\"]}"); this.rpc.execute(tsdb, query); assertEquals(HttpResponseStatus.OK, query.response().getStatus()); - assertEquals( - "{\"tagv\":{\"foo\":\"000003\",\"localhost\":\"000001\"}}", - query.response().getContent().toString(Charset.forName("UTF-8"))); + final String json = query.response().getContent() + .toString(Charset.forName("UTF-8")); + assertTrue(json.contains("\"foo\":\"000003\"")); + assertTrue(json.contains("\"localhost\":\"000001\"")); } public void assignPostTagvSingleBad() throws Exception { @@ -398,9 +421,10 @@ public void assignPostTagvSingleBad() throws Exception { "{\"tagv\":[\"myserver\"]}"); this.rpc.execute(tsdb, query); assertEquals(HttpResponseStatus.OK, query.response().getStatus()); - assertEquals("{\"tagv\":{},\"tagv_errors\":{\"myserver\":\"Name already " - + "exists with UID: 000002\"}}", - query.response().getContent().toString(Charset.forName("UTF-8"))); + final String json = query.response().getContent() + .toString(Charset.forName("UTF-8")); + assertTrue(json.contains("\"tagv_errors\":{\"myserver\":\"Name already " + + "exists with UID: 000002\"}")); } public void assignPostTagv2Good1Bad() throws Exception { @@ -409,10 +433,12 @@ public void assignPostTagv2Good1Bad() throws Exception { "{\"tagv\":[\"localhost\",\"myserver\",\"foo\"]}"); this.rpc.execute(tsdb, query); assertEquals(HttpResponseStatus.OK, query.response().getStatus()); - assertEquals("{\"tagv\":{\"foo\":\"000003\",\"localhost\":\"000001\"}," - + "\"tagv_errors\":{\"myserver\":\"Name already exists with " - + "UID: 000002\"}}", - query.response().getContent().toString(Charset.forName("UTF-8"))); + final String json = query.response().getContent() + .toString(Charset.forName("UTF-8")); + assertTrue(json.contains("\"foo\":\"000003\"")); + assertTrue(json.contains("\"localhost\":\"000001\"")); + assertTrue(json.contains("\"tagv_errors\":{\"myserver\":\"Name already exists with " + + "UID: 000002\"}")); } @Test From d667a929eafee45e316650fe52de7b441d8bf01d Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sun, 1 May 2016 13:09:19 -0700 Subject: [PATCH 199/233] Fix issue #784 by adding an estimate for the number of data points from storage. Also fix the average calculation when salting is not enabled. Signed-off-by: Chris Larsen --- src/core/SaltScanner.java | 73 ++++++++++++++++++++++++++++++++++++-- src/core/TsdbQuery.java | 74 +++++++++++++++++++++++++++++++++++++-- src/stats/QueryStats.java | 11 +++--- 3 files changed, 149 insertions(+), 9 deletions(-) diff --git a/src/core/SaltScanner.java b/src/core/SaltScanner.java index 9498284e00..3a929cd2af 100644 --- a/src/core/SaltScanner.java +++ b/src/core/SaltScanner.java @@ -316,6 +316,8 @@ final class ScannerCB implements Callback> rows) final List> lookups = filters != null && !filters.isEmpty() ? new ArrayList>(rows.size()) : null; - + + rows_pre_filter += rows.size(); for (final ArrayList row : rows) { final byte[] key = row.get(0).key(); if (RowKey.rowKeyContainsMetric(metric, key) != 0) { @@ -393,6 +396,36 @@ public Object call(final ArrayList> rows) return null; } + // calculate estimated data point count. We don't want to deserialize + // the byte arrays so we'll just get a rough estimate of compacted + // columns. + for (final KeyValue kv : row) { + if (kv.qualifier().length % 2 == 0) { + if (kv.qualifier().length == 2 || kv.qualifier().length == 4) { + ++dps_pre_filter; + } else { + // for now we'll assume that all compacted columns are of the + // same precision. This is likely incorrect. + if (Internal.inMilliseconds(kv.qualifier())) { + dps_pre_filter += (kv.qualifier().length / 4); + } else { + dps_pre_filter += (kv.qualifier().length / 2); + } + } + } else if (kv.qualifier()[0] == AppendDataPoints.APPEND_COLUMN_PREFIX) { + // with appends we don't have a good rough estimate as the length + // can vary widely with the value length variability. Therefore we + // have to iterate. + int idx = 0; + int qlength = 0; + while (idx < kv.value().length) { + qlength = Internal.getQualifierLength(kv.value(), idx); + idx += qlength + Internal.getValueLengthFromQualifier(kv.value(), idx); + ++dps_pre_filter; + } + } + } + // If any filters have made it this far then we need to resolve // the row key UIDs to their names for string comparison. We'll // try to avoid the resolution with some sets but we may dupe @@ -486,6 +519,7 @@ public Object call(final ArrayList group) throws Exception { * @param row The row to add */ void processRow(final byte[] key, final ArrayList row) { + ++rows_post_filter; if (delete) { final DeleteRequest del = new DeleteRequest(tsdb.dataTable(), key); tsdb.getClient().delete(del); @@ -496,6 +530,36 @@ void processRow(final byte[] key, final ArrayList row) { notes = new ArrayList(); annotations.put(key, notes); } + + // calculate estimated data point count. We don't want to deserialize + // the byte arrays so we'll just get a rough estimate of compacted + // columns. + for (final KeyValue kv : row) { + if (kv.qualifier().length % 2 == 0) { + if (kv.qualifier().length == 2 || kv.qualifier().length == 4) { + ++dps_post_filter; + } else { + // for now we'll assume that all compacted columns are of the + // same precision. This is likely incorrect. + if (Internal.inMilliseconds(kv.qualifier())) { + dps_post_filter += (kv.qualifier().length / 4); + } else { + dps_post_filter += (kv.qualifier().length / 2); + } + } + } else if (kv.qualifier()[0] == AppendDataPoints.APPEND_COLUMN_PREFIX) { + // with appends we don't have a good rough estimate as the length + // can vary widely with the value length variability. Therefore we + // have to iterate. + int idx = 0; + int qlength = 0; + while (idx < kv.value().length) { + qlength = Internal.getQualifierLength(kv.value(), idx); + idx += qlength + Internal.getValueLengthFromQualifier(kv.value(), idx); + ++dps_post_filter; + } + } + } final KeyValue compacted; // let IllegalDataExceptions bubble up so the handler above can close @@ -542,11 +606,14 @@ void close(final boolean ok) { QueryStat.SUCCESSFUL_SCAN, ok ? 1 : 0); // Post Scan stats - /* TODO - fix up/add these counters + query_stats.addScannerStat(query_index, index, + QueryStat.ROWS_PRE_FILTER, rows_pre_filter); + query_stats.addScannerStat(query_index, index, + QueryStat.DPS_PRE_FILTER, dps_pre_filter); query_stats.addScannerStat(query_index, index, QueryStat.ROWS_POST_FILTER, rows_post_filter); query_stats.addScannerStat(query_index, index, - QueryStat.DPS_POST_FILTER, dps_post_filter); */ + QueryStat.DPS_POST_FILTER, dps_post_filter); query_stats.addScannerStat(query_index, index, QueryStat.SCANNER_UID_TO_STRING_TIME, uid_resolve_time); query_stats.addScannerStat(query_index, index, diff --git a/src/core/TsdbQuery.java b/src/core/TsdbQuery.java index 7270c0c7c3..07a741457a 100644 --- a/src/core/TsdbQuery.java +++ b/src/core/TsdbQuery.java @@ -588,6 +588,10 @@ final class ScannerCB implements Callback> rows) throw new InterruptedException("Query timeout exceeded!"); } + rows_pre_filter += rows.size(); + // used for UID resolution if a filter is involved final List> lookups = filters != null && !filters.isEmpty() ? @@ -650,6 +656,36 @@ public Object call(final ArrayList> rows) + " with " + Arrays.toString(metric)); } + // calculate estimated data point count. We don't want to deserialize + // the byte arrays so we'll just get a rough estimate of compacted + // columns. + for (final KeyValue kv : row) { + if (kv.qualifier().length % 2 == 0) { + if (kv.qualifier().length == 2 || kv.qualifier().length == 4) { + ++dps_pre_filter; + } else { + // for now we'll assume that all compacted columns are of the + // same precision. This is likely incorrect. + if (Internal.inMilliseconds(kv.qualifier())) { + dps_pre_filter += (kv.qualifier().length / 4); + } else { + dps_pre_filter += (kv.qualifier().length / 2); + } + } + } else if (kv.qualifier()[0] == AppendDataPoints.APPEND_COLUMN_PREFIX) { + // with appends we don't have a good rough estimate as the length + // can vary widely with the value length variability. Therefore we + // have to iterate. + int idx = 0; + int qlength = 0; + while (idx < kv.value().length) { + qlength = Internal.getQualifierLength(kv.value(), idx); + idx += qlength + Internal.getValueLengthFromQualifier(kv.value(), idx); + ++dps_pre_filter; + } + } + } + // If any filters have made it this far then we need to resolve // the row key UIDs to their names for string comparison. We'll // try to avoid the resolution with some sets but we may dupe @@ -740,11 +776,42 @@ public Object call(final ArrayList group) throws Exception { * @param row The row to add */ void processRow(final byte[] key, final ArrayList row) { + ++rows_post_filter; if (delete) { final DeleteRequest del = new DeleteRequest(tsdb.dataTable(), key); tsdb.getClient().delete(del); } + // calculate estimated data point count. We don't want to deserialize + // the byte arrays so we'll just get a rough estimate of compacted + // columns. + for (final KeyValue kv : row) { + if (kv.qualifier().length % 2 == 0) { + if (kv.qualifier().length == 2 || kv.qualifier().length == 4) { + ++dps_post_filter; + } else { + // for now we'll assume that all compacted columns are of the + // same precision. This is likely incorrect. + if (Internal.inMilliseconds(kv.qualifier())) { + dps_post_filter += (kv.qualifier().length / 4); + } else { + dps_post_filter += (kv.qualifier().length / 2); + } + } + } else if (kv.qualifier()[0] == AppendDataPoints.APPEND_COLUMN_PREFIX) { + // with appends we don't have a good rough estimate as the length + // can vary widely with the value length variability. Therefore we + // have to iterate. + int idx = 0; + int qlength = 0; + while (idx < kv.value().length) { + qlength = Internal.getQualifierLength(kv.value(), idx); + idx += qlength + Internal.getValueLengthFromQualifier(kv.value(), idx); + ++dps_post_filter; + } + } + } + Span datapoints = spans.get(key); if (datapoints == null) { datapoints = new Span(tsdb); @@ -782,11 +849,14 @@ void close(final Exception e) { QueryStat.SUCCESSFUL_SCAN, e == null ? 1 : 0); // Post Scan stats - /* TODO - fix up/add these counters + query_stats.addScannerStat(query_index, index, + QueryStat.ROWS_PRE_FILTER, rows_pre_filter); + query_stats.addScannerStat(query_index, index, + QueryStat.DPS_PRE_FILTER, dps_pre_filter); query_stats.addScannerStat(query_index, index, QueryStat.ROWS_POST_FILTER, rows_post_filter); query_stats.addScannerStat(query_index, index, - QueryStat.DPS_POST_FILTER, dps_post_filter); */ + QueryStat.DPS_POST_FILTER, dps_post_filter); query_stats.addScannerStat(query_index, index, QueryStat.SCANNER_UID_TO_STRING_TIME, uid_resolve_time); query_stats.addScannerStat(query_index, index, diff --git a/src/stats/QueryStats.java b/src/stats/QueryStats.java index 757a9bdca9..1bf23c5d4e 100644 --- a/src/stats/QueryStats.java +++ b/src/stats/QueryStats.java @@ -140,6 +140,8 @@ public enum QueryStat { SUCCESSFUL_SCAN ("successfulScan", false), // Single Scanner stats + DPS_PRE_FILTER ("dpsPreFilter", false), + ROWS_PRE_FILTER ("rowsPreFilter", false), DPS_POST_FILTER ("dpsPostFilter", false), ROWS_POST_FILTER ("rowsPostFilter", false), SCANNER_UID_TO_STRING_TIME ("scannerUidToStringTime", true), @@ -535,7 +537,8 @@ public void aggQueryStats() { final Pair names = AGG_MAP.get(cumulation.getKey()); addStat(names.getKey(), (cumulation.getValue().getKey() / - (scanner_stats.size() * Const.SALT_BUCKETS()))); + (scanner_stats.size() * + Const.SALT_WIDTH() > 0 ? Const.SALT_BUCKETS() : 1))); addStat(names.getValue(), cumulation.getValue().getValue()); } overall_cumulations.clear(); @@ -610,8 +613,8 @@ public void addScannerStat(final int query_index, final int id, final QueryStat name, final long value) { Map> qs = scanner_stats.get(query_index); if (qs == null) { - qs = new ConcurrentHashMap>(Const.SALT_BUCKETS()); + qs = new ConcurrentHashMap>( + Const.SALT_WIDTH() > 0 ? Const.SALT_BUCKETS() : 1); scanner_stats.put(query_index, qs); } Map scanner_stat_map = qs.get(id); @@ -633,7 +636,7 @@ public void addScannerServers(final int query_index, final int id, Map> query_servers = scanner_servers.get(query_index); if (query_servers == null) { query_servers = new ConcurrentHashMap>( - Const.SALT_BUCKETS()); + Const.SALT_WIDTH() > 0 ? Const.SALT_BUCKETS() : 1); scanner_servers.put(query_index, query_servers); } query_servers.put(id, servers); From d50bc763c8c8481cee4cc9a1b1e639afa58cbf85 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sun, 1 May 2016 14:56:42 -0700 Subject: [PATCH 200/233] Fix the makefile for the AddPoint test Signed-off-by: Chris Larsen --- Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index cfbd5e4bed..d2ad17539f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -257,7 +257,7 @@ test_SRC := \ test/core/TestSpanGroup.java \ test/core/TestTags.java \ test/core/TestTSDB.java \ - test/core/TestTSDBAddDataPoint.java \ + test/core/TestTSDBAddPoint.java \ test/core/TestTsdbQueryDownsample.java \ test/core/TestTsdbQueryDownsampleSalted.java \ test/core/TestTsdbQuery.java \ From b085a72c3087943857a97965f66ecb29324834b4 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Mon, 2 May 2016 11:43:37 -0700 Subject: [PATCH 201/233] Remove the ZK jar. How'd it get in there? Signed-off-by: Chris Larsen --- third_party/zookeeper/zookeeper-3.4.5.jar | Bin 779974 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 third_party/zookeeper/zookeeper-3.4.5.jar diff --git a/third_party/zookeeper/zookeeper-3.4.5.jar b/third_party/zookeeper/zookeeper-3.4.5.jar deleted file mode 100644 index a7966bbbce49344a67438bee8bb0cd1fd4952eee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 779974 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

N}hR{0dLySoh!!h~(WZKRed%U$2{V7gCf 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 z zjLt*FiNyVMp{$-Z8k^ZQDvr?b7wG@BquBLRnpg;`%o~*FS@j9*wD6hvYoj{*v96VZ zRm#a!kpC}nE=}4H9JP8Mgb6I>2$7{%l`ADm4oS6Odyo`brBaeWdO`5m9>{W=q<#7a zvw4g`G2X&v(4+AV#Xnty0LLRy<;z9z{@=#&|IPpZk)#Bioy`7Kcm5wwQIL`PxXa%)cn`&jg0z3~Xy4TL~jY5Kx03n`hFWw;Y#{mWO?g3L*3UtLg+AD|lSxb&Q{# zU1UC*y72RDZGvVQE(wBKn(Wo*v}c6NplZPlkYa_^>zA>IzQubIsd?8X4=UbIVch_7 z93Tqke(bd?Gyl;qTksX)iSA?Xc`p-FmQ|v*=fQUs@tW$$Cy)I`(Be+p`2J|Xhd&&Y z@5Chosiry9r6AewGn^>>8rTlDx|)G$ZX7?$05V%vaO{ln3T2Q!iME%yA+u zF&DZVq$O6V;$pN|qZzDH=35zt=6~opgp77>bzI1UQ3=)LuysB{LOvWirGhVee7svAcnJL1W~a zbU+K_C9DG!WGMm{ku34*hqXuSWuXuay-L!3>hl{rN|@YDtGyg=Un{0%*P?`Dj6TD{ z+kkGM?y$=Cd2zjRk7?sr+@rG|V-k;IxL9A0e^~BSU>tRF%!LAp>^WsF+U;?^G!{mS ziti&=Jrv(E-$^(n855k178UX zKW4vFXL<8RQa_2{oL8%{R{^4poSY(?#pAuS-s9S+D|5;#1?$ANt&1@|e5PqI8jUG+ zn+5uR`gr%HjL?B91*`bv4prE=}$qQgoL;c#@HQj^7QfT z@l3&!sgskM)st0g6;0pEj3-C7jUb3h>si%Gou!i+8*Ar|3)Ll;>eGtz)e4!%tVake zeTa~coF+IGK4KX|Y;T3~U0OlDH$YFf0;sLj4@lzsDcv_%pQC};tJ+!K@|__;pV3HkH_bk| zh$|)dPt~X&Iq?TufG6w^A<<8bpkE?mQ~SG^I3Hi!0XE*!o$lZ(8pOqIaE8U5C4#B} zSc3T-aQM%{9S#Dk-cQNCxy)-V`0lwYPeh#S0#<3OUMZXpxjsVPW&hMaW<4}GA7wjo z1ZVwYvy5}}1}}{vYLV_Hset9kP=H(X4|wxm%OYe+h^R31@Ozu? zfhY|L!Myi2`$%L-jd(Lkyx3r+a4@-`Rg!vCQ#mD80=}N{S0&TYb#cmzu)9Gxnq)?3 zcz0ME2hKv#DvqYs*EZ}HcJ?}EfH`?$yjxWtGp$k~v0y%Y#IIFDCYBjIwuJEl1*V>k zG1k|Kpb5z`>sSg8A<=H+3l^c)!Ci{8k!hknL&1XCMa6U?qG8CWy&7>=iwC45C||Jz zNX2?AA=0+$F7bwjlseju%>%O_boL<#qpIOQR49?^@IjKFQR^z%U}}z?p+u<-dgFld z#0^tm-VDBpq`N1tl`v1VuyEI$aO%&U{Z%Q>$;I=tThXKWvu)*rZsrDvh;(6B$y-Cwa+s%`Kn<`x_HqG-NUT@ z@>wfq3)%Q9>NTU7hX+5#Q|Sqc5UC@!QfKBhC^U>}K0N0IF>N;cO}dH*HS{e@j45s) zb5$Dv^TL$VfjA@DtSJ*yPCZ%2!01MqmUmPoor;)d)l_|uR>e5N3ayp=6?$QXY{fv8 zcivEw*y*qI!;q1LePC;SyZ5?P3@PJ`RR|aj)TDlFJIi=7+oMPUAZvcm`T*)IoSh=l zdOX~D{HjbmzhqB~8_e9|{H(g#Zj!ym@v1Zi_N_I_ArIV(l+HFX83~fzfY|3#{ zR?SSAVyUHhuwR!f1;5I%8^>m$l3xI~ic+*J4OKst^$T2u5?wWrZ4JXK4w`T1OME2&-dmQU|bJSp` zRcW>$n$od4ZnkNR?3RGW`%c=CjDc1PllLTfG?* zni2{RV;KVHu3V(YhvsN5(&D7qRwbY&8m5n$U@zy(vt3S`LLX!iYfPBiysAtVNwXhj ze3+c{UxH<-P=b;hX#}~Qrv;cCz3FGN*oclg238BS8$#Ps=&xc<<1%%qZ-n{JwTQ{S%pnsG%*HUY7X~#v^D_wZSlVWD3 zo#)!`TUuDMd5{>)+}GEg57c*VSoC`oIG9vlt2N6#NSe^Qh}MysYqmF6-%SaogD}WS zS}@8Du8`4xA`{VNUi ztPV7_cP-{yEh`w@Jgo=3EDsx%eLmca&lK@DUN)Fm005V6px9t*p=?VbeeU`2%}-~I zL1@Q@xgpbLafjCtpVOrG*$KvWN$E+sA$evGtMhAl&(5Wlk%kf0=aruqJlfffq%l=f z8n0sU;%Dk!>wDZx?M4S3t}S}-B^Tl&VBZL%#b2PM)sJdNKUnE=B~1|Tu}1(0KPv48 z&TP;wJ$gL%?tv4Bhv;1 zp(EvOSTPs74kku7$yfDVYqC#6_)JHWii1IECzjkO`<)+N$HKqC!Mb0l)47Qi<8UMs zHsaD1ciVt)*d8Sj@Qn5F5Tkbwg{zxOq_78ln;uij;<;i>*4bNzeVW`3G_K%DC#Sz^ zHK7B&Z|q@ub^uPvsjFAsbOHW`(&K^3hdH>X1=^wdQmsdkyUX0RNf6AtY*Bb?q<3yS zYwdQUhBfp{5Nw2eOZm148E}~YZ66$S`!`{*KjiksZ^Bh~`E5S5Y%%-1ZC>74x9^yF zh=ruvID=V@Ei+iHH1crMWN5Q}X+nnz0(zmb1pUo-NZ;T`TtM3on z^IC7mA*XoHSi{MRV8>Jm;f)J>WKUakX-Ea|r@IqsnvJ!R=Nq!aNs3Zz3pTL!hy2bt zpb6Cqq%+SGdOHXDuxQ=D5s^Z4xbTl>yABUZ3o!XYh=4*gmAxPnoR|9)9ptl251CFi zBye4k3`Yd0n?E8f7dOb<8JXRodET1S7Ut@MzoS_mVOtgCuJ~cD;G+ZwqA zweI4Zr@^yZZF<)qyHyz$Xz>;V=ZH|f^BUQX4n7>TDCiV51{#ml`$-k}p(7aCdootv zvnHlpVC&THVt%&irFo7Zg+>R(wH4C#QPgJh1kOIdwpDnU+kvTJ{tW3trd8q`V_(w8 zU0rd2wT-wtU$%VwD}dLVe4KptS6lR%S8TghUXVc_`8|W=iUZHTTZHXB#{1FJsswXJ zm?1y-!|(l$8DTcpF6>Jo@`a@Lj#_qhzh0lxppvRHQOy@iw-eUaz>_@XAu3@j5^t(a zcV`;-uE+q+knaUm-4jW->D$B{%kZ+c1^aS%PDQJ+mbZt7v?^O_6>r$}xw6?|_6y6ig>H8Sk zMo)&E)c4;g9qBv58tg&V<_B+*DM#B+o#qM}h)JJ(>18TG#&ckjJGO!D=v#1Ul0<>c zL%@|E5ZlH);kwBF?w;;QkrYs(J*`6YO;RZPMn8~R@DKGM0zoSk{&@x1>jIurnW9rU zbI^izmi%p;{Oz?%GYFwtq@AY9Hmn8HWI5Rvz;Zz8#G@glj9# zCDrIE1e+`ND`dbIWYHBn+BOPf%DgMo&eh&e{J4v`=d~6DoC%c}%5fJS=VeYLoLOoC zRXIu_9=gtzm@2W^;yF94I#Uw&4A*MfmEW_fHX|SHCAEen&c6Vsb5WDwYvl~3Tr2DT zhz{Q@^=&u(5wFX@iPpor{WEP^&0Sf}Z89GWN&wLd(yx?rIVQDRBV2zDntBKkk7k^8!7kr#C`TB~5t`WX|Xkd$}g5J5~Z4@@uBPTI8 zPc30vG!j1cY+hZpf9I;*sUCV|AoP+n;k=t$)nHw)$Ps&?7%5loaGGKH9CK#EI{*}a z-!o_@<+F`5BBvVNYvdVbv)^%l9nqm0#^KWx%>X}V2r|u<<hMYB%9OkVHQ_wwYk^lBQH}nI_|Ge#U^S+|`WJgu3-SL!VEupFJn~;l z113)YzWptzy}F_fp??gfG+VmEi~99|8G^RNnFPWykun7hItbbsGJ;3+6p>Zlt7|r% zQCB7t?kxm?-T1__6CJ;bWbobeHu$Hc?2#Teb>9=7jAUzQ_>9Ed8ac&WWT$%`PG(>7 zxjc4%cH6`LmG#0Fgmi=IU{aPTZ2EyMAT1CA&P5YeysZtjfCbYNQb%bR8aaF{4n}h|R8)gZ3Q|@Ub;thqR&Z+HzZ7bVWgvw+p z0*dtLp?K|-fzg@k6+hwG$m zJjI+C`j8%yX*XmkV%%%YOM^vYI!M;<+2)Dn*d4L?hW|ngHwlzvj&^J&aBBOhp(led*u*&S%NXZ$y#_OxO z`Gv?iN#Zcy^fxFrns51+)-GI+w-cc)f8Sm!n7+$d3U0+fL1Wha{zmaWm{8stb`fRm z8Jo2ar!W1$#;(UG@SDZZbT#U4%LsRSrfn1HUOKm+fZjhMw>4U(BCP;Zj^_-pwfRSs z0#N^W3;AKH)5N3@CTLAbSEvwCe%wfrnf>1{ZhD(z2Y^wZnp{+kOb4fs1Mm-087ZIF)^NBz3+uV7q3Ht>)$V|C_VRP?9h5WQUvC)da| z9G}Q7g)U>`PP8}OKEf-pkQB0S>*w;(IGZ!a?8R6g0RjBxZ4roUZyKD>$R7pUwKyLk z-Ii^LuWVjXa;#nvQ>jhuZ}d0fP1#Rp-uH>tjE`%I$WoejoinOkz`UOm$vI;UUo z0%pami;M%7#8<;&?OY{xx-nWz=Xo~3rqLYRi4g!+N>JItsvQuTpFwPTlH#7DeHuo0 zWpHu4FIUO1ub{&!tT1*EZr9#M) zZKbh?T#eI9G>4lyQ5hN$Uqd9IMR=-Vr8K=?SRxZs8#AGrj=|w0<)M)0+_&AXJvaH_ z(rplq&t+L=#5r?XG&d``@y2FPxKX@uy(}1HTK!c7OqRL*kXR1zU>^&vT^l>oT&DW< zvNO2+R}si{5W#>eb-Xlzi40Q^;~eO!f%fQ`(luXzka*0KS|cWW^v99o51Zv(=>hz8 z;RhX8v)#xeI#;vgNV%HCrpn%Y9H}n|qNEQ9qJ%6Uml72b1PJ;kX%NzY7P>5GJ$;^5WzQi*!1FOTKP_1_Q29$fMgrN&mpo!_D$Tu`SE+9bD|}_WxCr7b)l3T z`NTR~a3a4YqE7V(Qf>Z?Wo~n@8d_a~s7kAXD>&{z8C~ZIcW<&;VK|De&fkp6GR%Nl z?}?TxO)6WuY36umC3Sv(2$T_DP~4=RZx=_86u7Uovr=1V7jPUcx?)8M1X(}_OgrSv z2zfvhW4ktDaDIhF~$`Y#66s+4-w zF9Q|8BnN!+NJQjS{>D8(Tti(mMtfYr@=8mr2oN?5<@;}n9O-2E zizxLYGqgZDsU(4$e7HqF=dL$3SubxbN-5!_w9GZS(>1W+toawlAQPzZ9BGMgU!Ri; zOQ3~IGI3+sU*G?--C@>3ayL&30^*?n0`f)r{Qs{q{Kuq3$;8I^KfBxu+U{P;A~&@s zK90sWesOl-ND%N?f-zaFMKu@78X>tDo)&=dJz%% zAwW>~YVn`^V3BqaQw4!hiE3g%tx9 zzP&JEUJMOoKLx?&_LsK3n&V#O@;M$}4gbn=^UIxLPCraLxz%|3)^$_>T+(R6(KDoG z_@!`-3)z(rTnn_+Xam=C?Sb;@3$_Jn$-Y&i`X2sm)sY{@!`nL~$hk&X+H~%jc*;TD zIvj*te*$3o+5tml-@;PgN#w$L^oF-_-}xPT;K-l2_?-jv?9Qj?m;Lpa zwS4V&)SjBZf1dPrU$1v}=<|I9@^ss8w`$1yZ?$jpu8%eQ-n4uS;J&q@cDn+7vz`J` z5tTwg=;L4ol!J?Q1V=E~*LTreO6}}Q=cpQ-Kr>)P;7u%-(>I>NHK45t-+e%K$kp%VlX9B| zuAOCUhCVFUI3TFd^G53sk(gaDONJ3iU8>$YRV-@{gWpwl@W_TxKTef$Yo72^&N%{I zwtkk%;(3Ew*P&S{>j15E4#Xa!b;-`29Ft(yw0==U@WEkjC^Ua&B#b+>%ryccr6PAl zbEtpu_9tN?v~jp7@}A}}QAU>GcSJZx9zjXE0$#E~G!BRA6(Y?n!$w$L(&B>2T*msw zHW&8XOD>dc7Q;hrdxCDxTs`e=BuwrB5#CqB(zbqZ+i)p-Aam(55KWD$!b_s)cL7TGqZpXniMupjp@Ll^Y;?)MYDuZyHr*uZ@h@8qjKwr|Z zMg^t8$LQkmy>bk?GZLU}l!t6jzv{MoK7Pm7;C)@E?@7^@8Sc=HB*xRjhFMB=%i!IFs5W+_Y|@>}XtEpW zl>Pn-$)8csW}kaXR8E5S1Tjh3GoVZU3&eXKOM4YcGUJ}FFMo?7qa8d=v94Vh%we&^ zoXb!p66T*jxSoeWSDx3KXW2zY%@zIe<5p{=+;(`9xsZ4n zEpisrGDp<00W$wDx4RB)Me_JPBw$D8WxJuSwWnL5aAvY@5pB6?T5KJSK!SJSg>f}> zGA|qAc+o`9LiKmL6>>0B0(r-%R;Q-Yyd1X(nCVO4$B|V~VyvWrC?Z|@!i-9-h+1`R zxJN;TP^aM7k&33RlL3cC3pE0266gac@A>-tV0{!Qk@^NW=5i%J8QY(9Y zPK+z_Ngzo9vRYXy=r5*I`bcEX6p2|&NC?44w6Sz&v>7uxCi~1;5umC^6n`xWsKco$ z2lWW=gpCXg^%Z_eVsjly|G{O?Mh3=T=gNk7332Ybqs7L`ptJFz<*W(XZAS;c&&ZGw za0e!Ucm|( z78?7F394vH%_Eh;g$zG9ymk4Rx~ca?QuL^A5evAKrL#r8yAV%{!>eS@W=IAZhfZ^l zO_PFzHQ)KfT?Z)W4p~NPSD2RxDLRfP*27f#Iv5V1SaxSmQeyl#=IR8UK#gLH7oFZZ zlb@)r(zq`;9H>0?t0RR^R# z9L6d$LgEtPspS+7NuIVbC{yYVK5gdik2_s_hfUy-%>Ry5w+Yj^c8o%*@aurX?c#79< zw0mD#y+rJy%Gd2rg6O^&dKEy1@x`Tzr^=^H>JNH^3Lqg6SH+8_OKE_Apqq-B*Ft?C zX{!qMaRap;lpWzGmgh_9k) z$4Oc75GEJ8<)$a*!ro3|5w-YHK;>Y#t71Iaz2c1G zwbH>4?`j0JJ2?|-u*vXoRefH^mYFN9%@i9hUZ`Vk zi@v33)F`f>3(iuRE9*}1_dZlm4~=3ZOvdZ?< zZ2O!Ipm@s{7Cm5!9ul=Ef)x4eftM1uXVl#Ao{zC63l> zmnWF$^66}QjrEdQo~2E9+DI%VJ%$TMSAZlcIh2QG4bqeQfN`aLFE07yeYad{%;7{l zhkR<9Jz|xdagk!}y|_pfjR9(|1~Jf*2!$vn^WLZ49EkJ^ZAmflL~(VYB{41*e*MU7&yb^wUe!9b~PfPJF46q)q=T*`m(wEh*&Oki$wSORc-) z97BNw#U{4?L7Bef?$$+}dF>UjMuz5aS~5KlVrJk-?Oty0ghdCppSfqM@&R@%Jp}|> zg@ik*XHV_}c_o4v;z*!jWAPFb$mkKt4J4$TQ0vBERosy)BV|7g-OGh^OGd}`F;sMk zYDq2mEl4970fEYJX;R_)N$Qk%mQEZNUW5s}FeXe>Bb*mu{s zf*6&qCR^0Dn4y%?PL0bK`ZTBIPNP~GZ#%5kSj*>`L<3Z++^3YidnBFCiMIBh1q#*m zR2FTtw!}RdUaX>Hfn8+IqSNLJE8`nVF{0HT!SOM)Py+#rf8dWUf-A|XOh(P5+cR6z zw9aBQq$%(a6oNc)2_NLRHajo@2=1lSt+y+1;_iCfmi#hML4q=S3YQ74sCAQ;)19Z$ zLhRv>GfSG4$2ZjG})ApE|LXUbG{{`o_l{P)_W9$Xn6Kt_onVUNu|TyVABud zx|R3eZ$MQ(BGSF$@c;UaOJJG^jr^rXzgNqYwJu#BfW0^X5~*AmM2}svixlQH^A>OX zmK0-^*`l+JXaD8jYS&2pILbMrb*6!7s4dZ1wVOypM~rTpXr#O{>A6fnsA>X9(no@p zJ}ib!NwDiJqf21CYj`GYkr}>CQ98YRSzN}L;L;0k?30oI3U<=Dn_y}fI3Yg-5kvSV z{B7pP<#jYX_;+YgmG<0pk1UwZKzFZzUiRlAFUe%NNL$L9ku!%s`GX~XqNhhh-BdAI zhEi|@EIP!N5Ckdcy9gWCUK*MSp6^`lR7=>h3h-E^? z{xm8CrLwM$nbz>OUOO%PLavM#&t>?qV@v>!OxjS>|GF@y((1V(7`rytm0l#C2%BdwH9ISlwQQw;@X{)jm(|71E!H?rvOcsGtM_091` zGHS-G9K|cBK|=iE0fVqoQ+6X}Zm(CJHOe3iypr1`YFo)m_E z>{#{A9+>E7y~YH?E6)^yD-Q|qAm)4h<(y5=$R00{0O3yKUJ5=AbtU`8#uWeHClGiSBdvXAkQ+ z@djzl9KiyK+~{^foc@9Y6-we@tm`nQS?pM#u3S|Y5jrD7krUP51j0-g?T~#;Cyp#% z3ZimXIcx2m*dK$L6T}X;fFkbSbX5%(zTB6-3UYVtr5_}YtSk4S`=CWe3;jyUQm=Ff z26lq#zYKHRdKclTKx->wZuj;$SHan#ADYli1sy=F_qLk_Q(UvD&N=>Jt6OG1m#_^ZYctks zFja!{-n!hI6SH5p@Mh{?#Vo@`yT!cca$bB}NSf(uN!|f>f~aK>d@Nyxig!xG_nsIb z5R9&K-IUlPeOaDeBWU*NTaL+nb71a4K7@(#!bA%VG3;mj2jd*YlkEL>)X~;MF*K79 zV=`WD^-$7*+&Jg?XI0%Oac01#d(KaNy7~;F3_ioy_O;JmUeAwfRTpaMqw;V9Dn%Wv z2;r|n7WZA0GxfQ&0M6+7HCY6sHtjf2DrTjE*UB-lgHVj8z(9HxtKt_zKI11$Uw*+* zWit3Ts5xdZVE|k_)~w9xNQc2j@=^DkT@uo+Z8)U@LZ}xLgYT)s9m5aWE%XxsGu5ha zJCRDDtri5F7XYaLRI#lb zC}aQwW)%-&?=SCUEK$^zRWGs6rI=Ht zsi00#Nm3zpGU5j8^_aY!Sgz;agSE)*+v+{rY=lY7+JI2FP{fgVU(ctm)O%SKY9xe& zd+1M%>h2iT-P<&EW*U2|73!7T%PtWU+l0LA-#*i))Eds&^kJMrH(A|5%9IZE6f!i? zow&_D|q4h$SthQ92nT>w?pfmXQNM%Vl52x+(qEwkwMDJ5ml8WAEt7 zB1~L7oshIH#9-{ooLiGv4Gz}1q{1}mdccb7QNGjv!P84A%(KYAY8_m8r>k-)<9#)e zpVwMW&8i3M#*Up1}lI^5(8Y`jw|}1S?NmKRbVakqA$|)k9_L zUy2LSp1`s}M(>1JXx1blLujVReBQaqbEHn6?J3oc$8O!F#UV%WW0!)2ch*oK9g}lv zn<%IPQ0uQ#Z4IO@p#kGvG*ucC!T^f;slu~d1~UBp@LaK%l8d~Q`H3Z`jTZIUe%7^g zvpO%1-fi_ctv-s3uhX&0~t|kcllz1kr!5Z zbJcXcM+Ap6dnG3!fwv^`cDPuL4Et{V{ocG)EE&4=fyjop|7F1n9t%>XTN z1-@_9;0dPY0@M{Us%!&(=ptmd4ju-`$UM+@jX@yld_d$)RgeGrE62^t6_qD14kC)a zd4Fy20As))sz?4BbUDj5vTs?@vuoolqOZ+*nr16S3nlcrx{f-xh@I%!rWe4-xn+vY zON4-W?~h9BC0$VL{F}CWgk0&xF0c?atq{5SxrIGV(L(NgK+=eMF0Z+CU-r^Qtwh;< zepsiea<*E28I;BBMcSye5y-fPZokHl-D5hfo3dQ-rAx#}>z^EAs=J^xkEAYMgP zQmQ)TAw3(eP@8=OJx3ryYo!dAEDQFud4`f@0(^#MvK4J)pTm2)^R66GDA%Iax!s8* zK_d_ZZuZbB5bux7Ngbl9T|oTkR9;er+|Lv@7>~$OgFH@?r zlC)hywgg`;L2D11GxR}AEFlL*MqTu;>5zdP(jkzyZpTL5@3tQAtQT6y9yAAxJ%m*| zjN>YBORoUTva<9VW zA{CvRYym4VFX-1cj-yXF*Out*DUY14Rl1dwEFUT$BoLXbi)|gKo~*E;u2sRif-teB-!U_T z5-L;NV(jf$4$%07Hi)w(%o}g{61MDLpz-3DXY+>!+@{c($ii% ziNG1_;^bql>Z2Ni5ej>Bt%FB@<`=R0MmraNz!Sf|C{j$H*yp8&N6&te+P|Y1vhHn% z%>anCOeNt(&_c1YF;?u9oP6Z!VsHD#vpuSrZoBne&EDP_SG!P&BB46;QRCo8Frryg zt##n(k#|?Yt_?fjw($}7rkgKV4?N}jZ=!R*1e>Hg3xIA=>a4wPgxU>iQ4Nfx0g{Rw zk+lwl(VhckVrg%>16fsSX&;0;L>ArB-~AiuHVKbyqT^r=6F?^e?zYi!D8n8%ngie= zg$R!!b6O0-y=y&eY1p(=qh(vi0hNzxYIdP299Ya@z+EH4z`5g%70WmO0~WmO_wny- zt2Ttwx*X|yhc+zSGSkpZebkrogFH@)Z#8`&{zLleLD5*od4Ef#o9X&s8HMYydee^v z$k%^%z$$fTj&J7SO3X%+9*c(Az%YXO3Rso#d5PUVZF$#9iNfbFl~`0KHHIj@iw1S% zJB&|zgH1S{aHz!}=H^*ZI5C+fQCHhVxl@*_>sJ{t6pyCmKacXYiS$N9g0&EAa+&_R z5f(jG*l3TVmxg$UP5wl`8Hi=lli#1q^mfnB?44PN)KQyKwZ)8ecTPXX^ zn2!yvGZB>1zJAq-!=DzI)+?~7y%?FL5^+C108FIh+h2iSzrh+mNZ6Y{XQrq<5q*};e|9rC&i#aDiJ(}TKxr< zA`a8f#|t?@{%16}M7h538xY#0sgtOVXpux}O#_>J!mOWzfb2BEPR9I%mQ-##8mZ`@iXMgLcv#9SHyMAtC3Q*6(?PXMMciP9E=WN$cmZ-~- zXPKKYUSHbBDHB-@$GFA+kIL`J)omunUD5c>4vBts$>zxPQ{PQESCljiR)#fX+B#2q^;`1+-)zQK&&gh>EJ-^eofGQl76hS26=aChU<-fA-LRok^_HH| zWYOXffcR%rOCLqMuSKXFRd4{R0TXR-=nm*;I$>a`K3;!6oExlWKkIvWAT)h|#W%my z{Ma{mxVYNCOgC&19eSbmH!^ADTf}zH__QgPe-)qEmB#|@Lc9nKZ@!zw`}#HRh1%u6 zVbvu223X%HwFwafK<*t*6Lkj+?O|TEy|HxCbO-T|bpQQzQ+pNthWn=VfnWR@%^h| z#`vl1cuHah>QjvG@+Tc%dzAvhQxl8YoqEPq#i9+R8Z*k4BQPg57^N}E<&>mETA2iO zDsl+u6w4y3Q!k9lm8dpTF%Juvlzl3ulcJckt0p6zC@@E@N`>ka(Ix_%ntCKOsp=Kv z64$6%@5xL%x2tlf+Nr6JfhVQjVw$x2igN0JZ_T1WswlCz!Rd>bJ#eBb+!z6$aUIC@ zf{rR~u%%Whyh=<8lZ@i3AGvGbq_*nHvVy`PD(VX{WCrUEaP*{o-58-zyn8`0ZGHgJ1Kr2>sQOQdCIYC5IP)yW;>h0>-ZVBZnW!;Fzps*pZ>2 z41aVR0B@bpjB1QE@ldh}A&<$o*qRgkkm*3R3m$%dklRrt4|Y3|L5Ko(N@-I9KsT>yMEJHYRq+S=j_iImT^PRy)4p)HCpYd4rziEjEjZgsd&7Mx z5VKEvz;JsMJtF~F|F517iEu1@g`f6N^bre|bvgT%OM`&-VOkZFr(|f{$cR}KYsLC> z1Jz@QqYXEgHoSihP6lh36A8NLs{9G{2QsE<(EMl~(q;yU39*n57}R4ZsKOv3N#_EY%@mOv*4&Lru)dMAoh%2a{M2GBz)wL#_Y&s?c42<^nGL!lF`?pG^Y zJNeWVL!R7}t}x)nO)7{KHpIZ&i8!BhwUp#bQfZRs2mZZ0y(Es&h>$m-a|7#F|=c{ zU|efwT?hV`(LS_js`bcs6)te|UN7MWBj8)rjSF7KLU*_6nDCvYK3*hIfO^%<$lsW{ z$veNi*A{!c0~N(%PtT(H&(R^veQKCc*PWSVA#}sU`JKqq1BNy!e3<5YMFThdZ*{ly zzg#2AZpsmr1Z`1DScT=((rr-B$sGonPH+yI?BZ?kzo{RF&G!I0H9Qhw9mvlV-Q%xr z!w-|fw_Q!(aEL@WYMTz7_YhW-#vW<+B206|z;bxs8y@9x1v*=G#1w1SL*1B`mCwvW z*4U(~!MAur*4~?~>%9{_bveOSa)XPlAvBI6R@}WCZK>si+MZxeslaL6#XTh4+(`Sd z$~5R|B4_I;xV)h%dF2mYg?~#^Lj{lhBkY(uHccEr6qh7Q*gX3&LHVKCNEPFc9AC=I zF(#+>Rs4YIPGHZ=dmw>R+G(ZZ7&gSd&DqGdOs|5^Gfhd%HRdyKULy8C(ymykGs{gb z)UxtqzLfHP`9moH&;{V){%~AE2+f_eoUIt4@k-|r ziA`96iV$BVq)$)${7 zw|hnoGFcfJt)n7cmj(FqeW{Yg84W_Fb0S+d-CnZvH{xzw|kCyyO7iY}Nmz2gp0H3_$AQTlL`~WKnk0A{BP4Y&-4vi4w4fcpA@Q zQ6|H#G8s(0QRkQI)BDyTSJAgBZUz=Ir{+_>QO%%Wh6K-CGHdGRB}pOXHWMOV4cntL zE5C|cwj`3FZX^#_n7s&QWgTt?Lh2P>nsK(^%e{U5>F3oDIL`-7;Jh#<>Z*zWzORR(p7f!R0er}_shYbxUoam^ zR@N+`c^6!zC%yvu`diTuG(O;PMM#^6hlkFN%K}ZEvpEVZXzxEq6!kCnA9Vl!FQJO1 zt*O1M%m12DqzC@5OZZ4!R!TwJYCw2M%-D!T$QE3(*g&zv@2VVs;myjNI+|~I0IwlQ z^Z8pmF^A|L79t{IVZ$iemhFG|HOfkW~s@wMEVLyMWN0XsYgFRe`UqNAn zXvb!=ey1YeI=9i?_?*-^R(J-UC3}?}sQEf#8O^X9%Ox3$-wJ8B^gPUV^S4C#V#=0w z0gVxcJs!*ODE}bD39G7Bo&SAw_Uj)02|%GlZjI`eZeek2ahKkjbKE+DMM6aa^9SIEX1FS;hC&F@apQB} z%w#WI$LH_$0e3`ai>`NA#fhTBRe|cz8$>cZNR`?qi|QD zN4gY{4)S|;@5Z^#q5BRR{K(?mNN%KW`tzA+ZyB@@znG>VrG2Y)eGQ;Ey87vgQtc4b zEc-7p0{VDH8?+zVT9zp!`mlIxq`p_4hvihZ90`PI3sED-C%JVMdz^MA8eAtc@*+wh z_k=5wxKg7?5_MXb_5`U_U@Szz6>Vam;aPs09Ibs7pUz$WbWTI6??(|NNGfUDvO#7) zRhrQM+(Fd@pE5rA3V#`CT}t8wZAF4}C1g+9RJq7_j-B7{AEt!!K;iK$Lv;vtu!o`v zabh#0D9-tB@1z3qFYD~kINssARNRw0y(()~U=Z%hr-+KiY2tox&U$jpbZW*uub-}` zVd*DYZ*zsLbofHN=b)y&)-VvB z|Gm$RwKikz|DOzatATg+XlMWcpWFZdr2qGC_nMO9Q%Mx3v`AIz9MlVw(ZKKwp>d#AczuFmqjXLFwq)cO7C0ccXyPKOb<3zeFa z#k)H;W@O3Pb+@k$PM@}QMrTg8uTD;(?0jIlG%8m6on^>Ei60`i;>34S|`hlcBVu)^0n9<;)E8Gs)|*FQW4_2z=>&a2_34JLZ1)Enf{5R! z+@AWpqV+|S-?$z9TB(BCnX+#bZ+l}deR(=##oH@AxOWF4>55-ko_QyQl9qsrAe-9@E`IF)8M~f95KdVHp4D^7LkB&vo57d&EP_ zJNPF}^uc_VmKP>C0EYQNNX@ zYJ317_DvAiH@of|no;lcjKKWBy7}R%J~bS@g9XJ`JYeukPJL#m_mv(LSM7l7uibZ7 z@qxj2KX6z5#Piz=Y4q~1+Mh!J5VVF1eUuQM zM~}T!1Zfq9ceTHXKJT*w^^apH_EitZ<17$o3ppyhE)(qMLx2tuhWZ9FGz-UzT`aM5 zAV64HLif+q+QvIy8@|)nJgi43V-4-v^5QZQDcjQ$bYRDw7j7hZB1bH{z90GY^(qmL zk1Vb2695_PTshmTA4AY_z0=Syrau)Px3=s?DXbvHxu}f+^G?&(+(5eiw?f~xwjvSu^Tg_skcqJGVJaf-IHYUf~Zh+?c(IAEc-karne+#uJ#l=@;783-IRPny8r1{_i{3CNG3#f`Ozw}Are)PJnw z$LSGN?PRv3HKK~T@T~YPaQ3}=$)~TmR+>b^>9JTi1T?tlcIz4muJVQcwB?EU#z1X7 z>q_nldz+02`|0b(b zuF-OUr1CzA#f+ngg15yx+!uOCH$c-Z;5jC=u$Ivt3}39LdXtt9(hQ|Qd*a+9CN?>MWeV*<+~4qrkqpPp4f8hIG2 z_U#ene(Whtmh!v`lQoYuV=K9pbmD>X9T>Vu%|)B-98#cAI_5ac5Qix*qy_u!`|WVH z)%5v0XGGa18(r1RS$3C@F-W!v0V|#o`b9sI()~0o>(H|eYyb$&0ooimmXYGblK*U% zWT8>FRHR5|pc6+<5oWCq_thwR5+KEeQg1y~A9}I0C9)9CVqN+`mVwyAjXX+_@F7S` z7iz$Yc(E(zqQu_(h5sYN0>vlWI52JOb&(hU8gopUC@RBEw&<>h8Gq4o1ux#^{BDO% zGuEJ-eR3$r#EigWav>av(c}0yA*8>(wE)OvUeZkoJ_e=l`;C)CNTlgI94CJ!ut@oc zfDf9unJFo>sPC5k?XIGY&Wm!-{u(ss8`9C1$*Xz4g!x-Q&E1HF$3RE%qM9?8xqWcy zza%=kH1!kO!=~ucLvPk}r|+!PJy3v!xFvx$ah#)wnatxKW0bnJwxeslg2ZfVw3Fx6 zR>UmT!coQ9aZk{Hs-3G0JLxLjd~2MXQo+}sUw4vJs;W9#YOnl4@}ly9XW2mYWQyru ziIGYrlVe00n9l4l6~gH1b7EFNbF-flR>H#-?j5PJ1GLo$9`|`S;9)oI)>Au+|fW)yf57nODlq{MBJ(HqTyclSyTgVY&e;?t1>f!P7Dya=0>~#^GqQ(mpd!S9Z4dK{3`g_=`2eP;va= z80#y}cJZLNhZi}{ngIq9mQNb{PpTn)*bm+D$OC>&@LdmM*)dq-i zt0XZyQdy#{BNr#KN`b5z$#o70WC#*|RTZi++~m^5zoDsN;o_jawMdWU*=k zB5oAy@ZJt`97;Cava*a%(JbPbm3`DA!kJ)ngj05mczw<1BUVd3=%jEtSUt3g zG5YQbsTtaTq_+DovRVUPnbWbqLi@Ds4ThLb>XsxMpxiw51draUwnZBeO;!pvVdeB=uyek|!-oT6#tw zeRUG3SuG+}A{`Dvxg&uVjJS@GMmIx|ASEW10o9SDUwwnVcX@ccMH14im0DV5gX{(r z;4mmv>T)e(-|X-qs>t}7TlgYQ;nSb3Ack)t;k(b}DW=GQ8{bV$9A`lSiD~A}wbL@N zhE)RC5bA90rHvY{eNR|~MOKs#nASN%6Q_`Kc_)n={1ApT}*)M}9&UP zK1apa81oFFng2nlyoj4)@OMs$DyK=m zE2@5|XDxJ~nRk{*sH3{zz(BYG0wyhqLlVX%P2-Xp6+^RxKe>_O5VFjpuaKaoz*whR z1ZWPDkG$h5Tm84*=P0b}42|7Cy$j-&@QH^BG2-?fbR{}lL1`f#+<0dJ7)* zws}!dpTO*v1TTK>v)}g~`^Hf2vy9=Me}c=o4y4F+Z~$r=y>^oAt_Mh}Y%5vSi z14qS21Z3*12C}yP)nRm@bx*Y6LhF%d9<4D>_F-xg#&SrIGe-QT;!q?dO#= z=)5Z%M!8h>3(CH9?}&o!3DiOh%8s;NRnnll-m-lao8^AI>8S%hCTD;i3IocB@?SP| z2P5C%pnmY=Lq=bS$^29wmHL7SD#^+p95YmwxRvR><%Q>VjbGi%Tq9%P2j0t8qhPOm zp8^SAgrDO;V$d4hR`9*Hi4CF>;a0QJPz~i$!7*?XAmA*5EEA* z(~<+*w5tw!)|v~SAT+mR1+OqPC5j86%PHjhLq$kfDz^rR_8hRu$PS7NBcmNGfYH%C zza+EDX!Xg3KGo|K(or-wU@DA~sSNbYg_5zBFIXC>0jKYVsD)a|GD|QCFjHi73}b3Z zmc0H=m_EcrZHFcP@$8)tsl%AH07E z#Lt$RF?m6I33nck+V}FHd1Z~k4xe%iuii@z_GLSETA25715`Jh8nUa}%rJCVtMd(R z7*NW>?dhKQU^Ch5GJZlcT3S6JXzH-TD)q(~QoZDczeC+SFN>s`r}1Hi8E#?BFrs?$ ze84lq^gUCx}g`L-W>S!Njr-X2%9kq7eko3 z0glIg!1Z`=LjZ8XxbOoCJy`o;>wU8xIKE$#*WA9MVH6${T;&+kNP=k;;S`E+8f6%b zGK^*!Mw1L9iBK$&7>>>TNW@I>;w$`Zj3>$&)Tc>Za=|XCT$U@+9c{~lrpN4dCyl3t zc|H*9QvNO^T#x7Spzm&sihl^Y42UaP(8Q>u)gf?F04${nC=GfaHcAgD)a@#ZOR{Wk z#ixvA(chAtn#54^{Y4NP8Y{ZZlzmp3*&c-X279Z3y2XX&)k^)vhTb9jtglX!(+xG&Pod;}0TYBI7jt;G9 z{2GpXjua^Rno3$19D*HDg)nIdeWVR4k6`9{Y`x3iQ78cyN(iS?UkWs#28k$PBZ~kv zDMSj?qJ@G{<&L`+ol?Y~xOzxAKf{MUA!a?+^vxd#9CA4!|Ncy*fLv(|wGUwl;)VqA zo_a9e@IrDUH&6q1#|t+y-pI^C$9kqSzNEzp3`W8bf=NaQ?{!Ul9A^KxxJ3?F#+4|By>aIGG2? zrtovvlUrsRG+SsF4nK#5tcsL(_{=5oh$QhqP2<)J#=%#!*+8pY8Z}QpECQ zDfxA0U0c~NF4|MH?+E85N$$(q)ILp|uAU$@e}G+-%CZKqC*j^q!|834MU8 zQ-h&HF=UZzO7*Xd={y9g3#546un*QrWMY&Z5+A-=aai&=sM+v{hQRE<7L^rYBe^yn z*H}#K9ksMZe&Roqqy>}Fi!CQ@Evga9GPb73TpsXD|RN^D;dsw}#40{pHeO#Vu# zQQ`-4h0X}qj~u*Gnw+Px0{|D(s*Et&nW*v9fa<0>4xKPLPMk7n)COTaAg2?m4v9S| zRL7v33;UlorJjJ0_2v>ye|} zhC~&pR4NC!q~bZ{Iu?82UTJq_n7b_INVGV^SgFh{a#^pzHHMn*p(dm5#R-&(y;S7T z+mys9#ZtJ=82(fgUlH1cj03j=3s1Etzj&!Ym5v$0h292-())!lp^Y^_NZ7?^1q|xq zwL7rJdd0itn+^)&rRL7U3PK?B8%9L#&L49n!5+6Jkgo-5*M)OB0qYR34;ghv;@AV% zB+-Y)-ixi1wF{*8fId&!A|$jA3U=bgP2vi50~6(PnOVMMoTQ9})R8bM$&~Yuv0Nh% zuXfOgF<%ThM=j7`Q5qc=77dY#U5&nh_^NQ!OZBl~pJmW_BUb?3a48$Q&%+7}uW@q7 zbm0Y7BRkB{(RUjhOoi?cJJ99@|K)n{xPYbjp1;^hMbtmhJwEA{B-Uk6-vv$|PuE6_ za7{~b!JTDgE_UUJZ^G_dJv#!*zBiH*vnBc%-Ng*0qZth~MlQ;H8K?+>aGRfnt^pwt zJ%yPbrwMc`Ngjme939Pg9 zW>5=zo8h#F=TTb=y3+}$pClbf1UJd$lM9>qxjkMp#)WO5Za46>ZU9_!T(&7@(*)>t zD&`qXYs$$!nZD^2Ld?D&+cx0Y3CO;cPj=xWzeKjpLpDBLNWgqbN%?VyhZLb@Q#uhu zrkq8q*)d);ai!B@CYT~xy@A+j3hAwB~e_g;9Et}6{xoB1679JTY6q=zHbZJafh<) zG86^bEK1azjP(14KwTA&AB3}dV6RM<3k<=T;kFNRSMtkDtZf}YWG6Dc5j!yZdZRzx zl%<@aUD}s^^Dg(NzrQH@W0wlUF7L^GTo-(QJ@U$YfuDaEUh+$P343t+%uM{aKT&$P z^n&BQqD-dP?hyQOJIGx`@$l(A`=4a-CcT8_9FPD2R>%MV)c^Ovva+@+rZFX9M;pF9^{+@_xi7rhF^;)#e4xl_P^7iWZi z5xG^oz)w6~fZ!wh8Y|9-96Np`7rDLD!WknceoeXZoXU?ogyg{-G$(qs0g<0rjD*Nb zbDj&ivq70+L>XZa8^VYsqH8}c<;NU!Zt|lFnwvP7n>u`0>>-#zFItbF54$U+=!>Zj zxl@Hr{fE0|QDNPlyDLJs)O;#Xv~&GWRW3SAYl%sBX*?rbRV%nnU07C3v-T|y@5G|Q zp5Fk61u4yvTnk+HgkISy%(C>#Z|M@3TO(OHZOiP8Hl0A5!(HXlfT4Z@gVVj42}chh z(J{S=qo(Bw5LOYY%>CLuB@yn(I>#OL6zj3Xl(IaQi8FO+{)*DTBzD;^mBVJsSIRUs z*Dj^o6s%$77#rC07zXHNSFcFhu^b;GjM(5&0C()R7!?&ON4i+QqH zadKS8oTF?ycqE~F@AwH_*4;Nf_folT+eWL-xcq?!nvyB$tyoqm1i}Q6j2nvGiGF#FmS00>zn$X**F-&Pvp9LGdq`c( zkHE%4LygLp;I3M<(gI^X&)qX9`Hvl*Dz8p%L(QCy+w{{EcdHdy^j|YeDP*7Q&3m<9no)x=2a^U`%MKypPac3 zy9hokynL6%PD|wJR9g+b{DsYNIk~)ZC)HWh;O3wCKK!5{_A-&B#}UE~WdP#8N|^;Q zqTN?L$x7KWmGV!>((_V^@?&T^*rV(&Jt6j&7K2>4>JrU(>X#i-S=Ei2;z;UYceJ>B zqfcC$CZ548wTnLB-9xW+aW@BIm&#d#*r9ePpHL+_Ar`9o1!qT&B7-7)uIjaqZb9i7p@%zX9-u6QYJ zL|(jtx;dr?>mQ=RR?l|vK_u#j-dW@J58AV`YXNRPofW`G6z-QG_llaN7 z9)qDaGhbxP7nGjXG#q<>!x#aZmLXfMHS&ypW?@ER=lUigdu!%6rC%>Ss`pSG+q-p# zotitRggV0K`i}Is%usz)5`O`#zfV!GxoR#s_$=~SbV@xsZeA`YfuZAR=D>?Cy3 z)0*TEv!u)XWi*k`Q-(@N^?bIachAC@z0<6hq9l*~yY7Vv_#~~i-za6M7j~?+GAV23 z%DGq!I9Auy)egLs9uo}yj!%Kpk#D`lat*|4ZBcptBfdLM^R4whsd6UkvP({MvNfMa z$A37zS3=tjw>OA$%>P%2;jd70VxOr+lVCKV2w^{Pa&;zE_;RWa!aLQwrjPoF9n{Bq zz7>=gAGk)|`v&y=@D&i|7WZ6`L~P)A1ko~jOFbuGT)PUt~jWuE*RY!h?Y32m|)iuf%_z9Cz??q8kGoiEq4XQKu0JD zpFb)7re7V$qZm;6+~$@dfKsW;P8!08X{4?hvD3j9AaUDrrK;F-IRQE3EfrD?HRL@B zr7n-sS6Fkq8w5)aYokHD8xL3LR)h$fRoe|xQU=guFNNSs6hR~*ubIG(Ca?*VuwhJ) zjQK`rjC2Ly@A8rgFbfADtn#Xrhc>Xqkvz4ANX`=?iN*7c{+Jf&iF%uX3>G)4w2p!O3YEeL*8v+{qXRYdM)fyI2un> z+1c0PX|t*7et$VT2s z6I9Od4UdyC2zo19%yD5=IYPO`a4YJxYt)b>QN&sIfkCYXmNzalT^+Hr^#kH#ZSA3Lg}ma~ktfu=qQ`!~mId1(;f_DR3&&_3ZjmHBLyw&?#eNjG z16BN@#n=NIZ)ocdLp|e4Km60YIcW1 z$2@_X)}ydcT4T-G!6cZcWqITVR*`-M1NLQTMfD{s=q}Q&9+%EBwS<0 zpSw%EX(U<#$8}a!+I64D)OaBSNJpQsExo`tP~!Z`)4W*5;0@yY^Ps-q}kZb7lx+Wd08S>KysajhEk? z?-$tK=ZCa-qy?S-nB>|5e9oir`DmFR04hV1V1TGK2 z@>K?0KIMtTaDaB5oZAyh-u0GcBabc`6E2Ukcx(_AD6nx+?$qCpT-~vCv>As-n{JuI zBQD0d&>N6vYS4V@9jDWy&gT@e>fzYAkA^-$p4Mf+m1xN4P3L?c)aimKuuJ2}3lkk` z6zq*AY0d~xM`Kj`>nKSlm{#u8Sagmj0&vo!vQRMmT9i!N=>+JoS_27)`U~qYL9-*;zG5O612aJaHH0vIK90ln8Ub)N-wwviOG@z*K16sBt1O^K zcU^#g=f#HYui=Ed<^D`YzGoPa9zuxdU7)&UJc=d6v4Qk9W&oP;3>RjZ>4hM8{egG` zE}Wf05(DzWV9ii18*(7p#k>ZmoSNnR^90DebZmG6P=TQ)(5#_q*)gcI3fT#Gx``&4 zS>uLK{UA>D2xQR9QH_ufDe5O(zYlbj)TWMX5Y@{%(pC93FhVzdsU3@TGff&3d zwW+}Yk~e3P8M|@v4#UA04aTBBe;bAbAs$8^!zwvkjGevoR!EmS5VWgcE^7>-B#Mk! z5+#GsO^*==7`lYc+T&O(8al2bKLQa{LSp42Bt)!*f3Ix`!FP@MTn1|JXD}I&eeUwh zuQA*SI~zUvn@I8ehhUeF^~y9YZ~q(rc^8pz+nbs0NKbV$*mdwS{BKeNU-sm5?5 zXMm+_5k>~{PO)JGJE9)W1$!paql-fdk#aV`Ua^kNeSh}zAgTytI8T}6{UuWd|pU$eIEGxP>G;Hga>g zl7II>4546IlJQDJbj5fAwq`#c?q`tyL0d;7!@`208_z=`@XYC-4J0`c-_fLilm)gPBv19k!mP6_6r)!z2_HdP*{^A_l#R_wUiylJQFLF{Djde($aqQGM2YI>d#$fi+ zPSN(r9{uCu1@-XC$(z4`-RM)Z(<5*G>lr?qk8Hy}e*X~kPpnVUn8xlv+x0uh$L?s$ zwNDt}+9ZG09dgV4@26;=_>s43U;NF&n;+p_+JH}_dyz%l^oxsM0RHmp6qmnf#`*}i ztDi)|_E@)TSDu^2XTz+1Y4`q@AMh;2rF-~L;csC~f%Yk4X!$BFm2Zfp7EmeYxm~5b zrIR zOEedSYVMTwu#dte7vP2!R_+78cWvwk-8VmO+MVD2MBpyh?c4QeuX~5O;Ey5N>YpWN^%E}VHvQ0Q{0gCI9a26Jixs!7e8@gbw}&iif3>BsFRAB@-CKCm^^3QB zkEknP$LvZfutlv#Tlp<%v{4M=>KL9hvj_L<13u6it;X83-^` zRULB3tpTeBoE;Y#oDLW-V#l=+`0`>ymD_4a=->&dS=kTi=t14T3SO(_ekg6Q>*~mGc@RSH7q!pJhfKGXb@qvV}UY5ONh>CsK9TrJPHTr zPbLaizXTqD9*smoR245A$BxjCfXTT40yrl+NZju#*co&sK8mMdKM`9grp+ail`&Y^ zNMS&=Q!NUAk|9Ng9UWw;W;VDAi|U|hh=6A?d(KwtOYawYYUm>ZpnEb}8$GZ<&A~nJ zwm1-~G>lw*?K>$XSavVk@KSBlM3Ik}v1l!5^lY3-ixi8Eo5MggoIYV2ta80ImRT4i z!_>{k222UqIJ%R>nJof-qqGfvTM`Ch24T{##+*sFsXJ;uBO`=x*E6&QMbVao@hBtc zNV?twYlH`h!G0&oGKttEHu5cWYLLDT3g&isXBD)4oN!fq^eAX6)EJreF_!mD0_gKt zK>MXs?ZztI9f)f#N{zzx@brjgs1l);(y>=h!efF9WZe~1)pkrMxsoCb3(uODh_;QH zMn2H)QrvG@@JMn4fNMAt;rGXd${ z3Q2c6X{vnTSQ6FLKd2NJKbk*Y;zq;D$@n__>KTFIq z$(G!@;v$_adR;!#QpbrAjN-eI7-NU<_7XMrgEoL-qZR6SM0GqeoZyu1`9XcCdqn$$ zS;A~pML=27`Un!pd3MEHjJtvS*lsK_7NZQvHD`t>6+@H2CftHqwc$3Mb7c#smBY@= z^Qc zTBZL8WX~1Nmt?7!n}R46&8~~`)!SD#<4uX`XPi%R3-AOybv(f<4oPN`8}C6>D;OnJ zi=m$9Lmr3v!=qM=)RL-_S*B9cOE&1TyK+jh)U1Iz)gu#}MaDT-fLPmChI|CcPA_Gq zldCqZT8&lfdi1!c!?kobt$9@x{tlcwJqB)WbdN)>JA28bc3fTZ;1^T(+v9KCGWo*P z3muZ*H4I7MA7MJ+{5|>%i(%;Jm{6=5d?Df(A7RP5tjxi(;Gzv;rwjW_Q`p)P17lTW zrzNLt3F_00N!OBeT{>k!URzYJ0R5yu%`0r%lD~O}rYY^)5+}E)`l#EL4}M&# z|J&%XEQqg6ezpkaHZQhXnJrEE=fMJ*z63*G2vScp`%P+dD7Ph5xFzPSY>cjjZMyh@WZmn+2aMj9sXG8Nywxoox6dJI(xg7E zl4{LVUMY7f&YFJlQ?E`g7V#MtDNy!)P$>%+UK{1>CFRQv9zR^yY zOEawIKm!?;|Peb%>K&*^V(}PLPAqupXd_5{&6@9vx|DF7UP$!5@+ra?B%& zE}XcgxIFMe6Kc_UobkgT;j6<_AoBu8L0>4H;%MD*E8bfl*sOvkH{Ff;s3D6N)Q8hD zIpbDd`_>4lZTi;Lr$11oI*CXovwG1oKNAK2O5R%b)P=BRH=#t7m&MPspDdbzR#Rt3 zTH0v>q=N){pfnR8Rudqt3^2`<)0RnI+VaiN>Y7kp`Ny*`N54!)Z329v4W7zLa7Fn* zxiw-+6k~1COC&LD(izw)8f?!O%m+^o@N*N)U^}c$m z3r@!_NZT6X9=XE{NkboO&mQ2ZJ3{`=BAFLFN*_2DKS-*7fYF|~i7)=LAB243J_?faL1Wr|IG4RTa>_ zP3;jyC+DOVHL}}YS5@z&lg^l9fBsi!oOfiD{r*LackHH{y`|%TaU!E&T3K7uXTXnh z(db*XD@lzG>S__fVjRgXBs!H)D#w)|=2E!=GO~+HUtmjTKGeo!sm_2=LUj2;QEGuY0Qbh}zURzYQA7@-tO1Z{- zm;<=9KXivHC-<0~9F)b*GV;xyOilGBT5g0T&v7nX1(p1yle)C1x2zuGGC6=L3_YvJ z7Z|!)_&A2a(tFWe^GlX4OAA13ny0%;PeTDJG+`@*%?=dfaJ$D(g1E)q7F4$POgNq3 zV^sm1^a9XkL1jg~`Fv-2;z%jupjf5vf|>_+ibvG?8dCSjX-F{^NWK;8oX$m#rz5MT zdm#A9KYL5Wnn~&sckz6qlGTU*(urTy6k=|}-Dpe4_QYw;_v?(PD}w9C@||HtJ$OCR z@pYxbcI9%P3qG=ScSh$Oz&-+jUzk2}j&atc^uk5i6*ebA;*){1E{4ue#oi7#3qGkH z-}5NCZ=%#MSyMFBm@V!cS7f5h>#!zYkBS{7=*AnHs+TU?=1RIJ2kiy(B42_ODEY>P zT^gXRC%^hpf2C>K^_otScG_K+(o(^izgry@l*5h{)_}yG&0ylG`b{p4(*ATAb6Rw# zN{?k%w9nG7x$%gIHc_qFNi4R(#M3643~wDYt`VW?h|}#RCW?${yWj+PD_2@KeR9W( zn%9b!^90Y*QK`9z`8E+_7h(&ZlOc3r0_|i8R`AGL#E4o%eNwL`0BtM+Z8Sl-aG!4j za=8l?bjwpGV5f46p7~61B|X^21$xZP_r>48eZvTIEPX%HQT0cbj!we6MV@>omFlHf zjD65=Ev5Izq7AtuY}G#MFWjI_RfG4CC%&1h7ZU0z@=EVYe&NWas z7czUo%N~eN49A-;mq)Q2K&>b+Sq+I$`^y8S-4q2+)X2lFOk^y|D%J?!Gq!v&=yvCy zLWAerSv&(weMD8?6u+fnvt^0nFMbUuN8G7n{K(+ntLbP@>>VPbcYlNbXJ9LtDtny# z7ueeQWhH6<&)1>2-m(`hs=AVbAXf)oqL3m2 zHJz9P?v!B;msuDK?Rswfp>@YCpby2J3kYh8l{(l^r|;VXIeoqS8~~hxdIcp7GL`#g zW2_5KtfpbA0ta5BW5{JCqYZZ>9*jpp1KByI^CX*tlaOJq!sPtaM+zi-_s*ZNJUQ2! zuwKWcZL&Rs8}~) zYCpW6t+@jg-XDq2v|tkpZy%%)qmJE3V#K-Av(57aTWcbT^*6^65e%SJf&UG!uHW4WuX}cGIjeD5w#n6qAR4 zU{Xj^|33@tpV^wX{u|-k*!fcu(*YDNz zpiW3Zt&$b8SJS9K69c5NvGvgB6;RVnA)+Vf?AUOgn*9g)5AJ|L6RqMm=e{Z8-Px=m z2=kJhPj@pro%1{C=WM>e&tCWec?R@Akoe++MtWknnexOIqRoX&LQaqg^Pm$^jRYe^ z3DZI}(2Pi9R?I9kk{j_vx%r@r>7_n88QzC6AdDHJWP1|sIE7s_byt`+f1lK-1GP2x zTZ&%+wl~jHudV;g8=$+AGu)bPpTW(oWLTYZIIXO%CV8%;IVRENJ+rHzF6zVV<>yeM zfZGM7aJai#U_k^3VM@K#BK)MqwRyNt=_EDfjRfmC?^aT3Rra30>m!q+7Cg?WHAR>0 zVEhiUTT~OTK?r3rLHKNfN(k`QM(VXrj+eCsOF(MPQ*nc5vWg|E&PTj}W&5~ybk`KG zNZK&M_JAr5==zk84dad|?Wo&B4MB*6?+(g?0SI40Fi?yk7{h<4*gHJO-Hp0XnQ5*1 zI+2lSVmx^wI^CulYK5;mjH2yEd{6hg_c`D0!S$Z&{u9UUo5s2v+G~%|N7;uLzLZT2 z5^2pZ1F7zUTYb7uo8l%O;BX&iUpw)C{>qs)B9o4_LGHi04Xp~36V%GiA*tvKC5#5c zjIYNgr}LRHtk#>kh43TMWQvS~b#F{}dZXX`H5G%BwQSu9PxT1X!x0PW3yK{YdN>5yLBw?x6(~ss8plmSCjg4c`TO&9Re$^)Bc6`sOGB?evO z??N~qM+L{EP`i>pDU)l`i@KXp=!T7{AK=1TB07;zqgSlpAFlzfTSJ+4lMf)2PLPPV@b&qW%4XP#o^fxWX?#ehw6p&bah zGXGK>)1D5sL3(~w(D%7~ip$(o;wm(^nS$k4SdTA7p<){}K@`}G5YOj;Pz3i&Nzv@N zThY*5#hI*(e(I#}!X{ecl5^akaUB>&HOQGky+h8dUfKbdhS26?QuxsxeT+XkjEn9r(9g(?+ zM-fgBc9cfhJ6HZ~*;`BmL24E6Wo+!npWSX%X`jq^66jLR0%m5>dX2g9ko9uZp&7b+ z7qQCi4q=0;#O(sAr9pCpFk_7q3W)3ly{Y@;)*)nz))*6x#nFG1sgJFRqB>V#`UL#? zF}u!WG=?cZFt8rGGbwBjP;0MKZ1e|BN|u9G~$CMY+Bod47U)TeYyx(UQa@N%rYkl7gOcC zZe_j`aA&^pS^K&s(0-f^SGlme&!{`hx@kLnyBK+UT8a_)xExSLawNeWzlU^EY&z@)CM$GXN;`ILfH6xshw586p$nthn({voA7Yu5Z`s$s6z_QO(Zp5MOS<)HWwr@txp@ZD&I+q?3bWi8?Ztf&z)|x+ zF}YF`-F2CiIuZ#coo;2mFOIAt4t@rADW`9|^-&s-ansQE2ETw}g(%B+Y~8!Q?R66y zC~Gd=CE}F@DRQq@k{LS!v={9&yP54j(vmeCoyqW;S=?TO$Py5Uf#coL`ArO+@-W*r5muGff7=%iPu$UXp3N zL?G44Ddk-AheMZybTpI|J)Q0WCFr!HV4c+%rAN#oq-x`h+vcxZSS-;glxVqc_`acc zRh95bj2yHbO-5gp&LGduWS-PDvduKj$>wI$c#8DyUb5NI*^C}XXEo6xCdt1W*X?%+ zx`Ln4#Gzx6rw5s0$d->?!w(b$vsp9n9DRj&5xU%mgb__B!wVRg4Q}j}D*Yi%l6ZgQ$QN;!0 z+`C-A2&3Xff1bO&58E=3VKNWO2!+(Q5YUB7BwqN}e!uJy+Vg_?dx<%*9;dX5Q``Mk z<0^}Of+T{^JF|(QD+(YaU9%PE&!=O1J!`^CpL!L>ktpu#<&+;nC7MJFu5=?&;&6c& zqe$df9Y7`0;rcmXNaU>@Nsr#Idy_QQs9A=8<_$8Lgv_bdj4B^tU(8KHGdC3D zX6jyrci{I&!92j&g)*_jO<|lI^oh5ojkdLe*xIEk_+Xa} zVVo&5&n4cA4dgTlp6r|EOL7<0oz<-_hWNs;5Exl|`5bU5^2|HHg>f3X71=n6*B6B~ zB^y=b5`-ZyO&N2rd@yw4sA$9TNJ;Q9PCA%Rs(EjG`~zQCi!udr_F8?VNP{)EIx{yZ zUg@%XZL$YnV)aX0b1(i_p_9VGMb8A}^=O#?Wk^F>*4)NeQywT%L~O0?tc;y(|FyhB zP0JPKGwK_8s#E+(y}p^BU{D2(59xLE9NkU6v5_ksk@RQ+NOr{gwD z=f@8UomaCMOS8h>`j6WQz%y}CPnjd6cg%!iNohROr-83=pAnC?WnsC|WuFb19F*Zql@MW5TFXq4ImQYkBUb(pkO>~zdFQ6y|b z0crzs?N!^L-~{2BF@e{~K-SOj&kO9nwuTM}D%F`hV(VzuH%cyBSPNESIUBcX@EA!V zCw0mMDuWRz&cuhRz7b30{knyCB7j6sePqetYz9DY;gEX1A7&v>x20&UNZnI@SsyO8kp`}(OA6wU zE6iDm6VTGn@aw}%9c@%+yTWSO+Shurw76PY`-e0>@HD&*;+~VRn5@iZ z2FoSLyhDDk((~(Rn+QmkxK`0-q=_U~XSR-$si_4A#1oBP=`pxR?5sqk875A$MTk!@ zm7&MMKc`5SfpFj_TDI18dyXq7P?fI?wo3_Rmaw1YF`2_S+&!T#EBOibeUQd}vKI{S zbB8AE6oNCRHDjy+Zk_A1uHg6Rk+8_v$1qU7+ zC2xl0*P!+WT+ok{;ll)<3KaHo<=0j!{0<{EL5d{B{xJC{&6(9?MTkb zsCa;02ke)!`6sT;tm-JW+t!38X>mu7H!9SFpv~-8KU2rOtJ&l3KK=_+@8bB=gW4T& zip_GgK5Cv%nENT_1ZzeD*~1XCpntm3`3NT@D}| z^3lW<3}lCFS>51{62Sz~mU1&b-C+#o%kG-Vi?|7Ls`iEJ5tf}CJyth^(?O`-LQAxw z#P@I@*sy0BE8NrIq95wD3|$Wanv*|fBox2!5w)%gvC86ZZ@}2bTo3A6JlO{hBV+E^ z;^uQkMMkhnz`nk;3l2M979BO9{ZEVCA?T%Qz&v!nXzf+Z-Gh`4+N0*aoM;EkfkX zJ!efuPU5*f{{DqMF;v_dt8P!(#fmnhcVz(oAu(LD@~2l!? z_*Z6}w4^Z*Y?Xs;tGvb5_-}5ZW@^xt;igV_o}|2A({%fEPv-E$KMSu1P+Q%$+~A>W zN7T}eA-M&Vu_SKpUvLHUO1-+T>pz7L-@Kq{qHk|A(|w=W_pLR4n?RoT39!jp8@QoD z_DnX%m={0VgC#FkX=Vvl(GD>u(8t;@wrrg>mo6wv@K2J5QRAjkzfwl*cNL|ADTdsd zy@x_+fy_p-4jNj2-@C z(-kSxU!`?Y(wYLUG3H>lEy9@}IQlXOU$N>Uh}z1mGH<T0zf^M#x1qUgYPzu*tGIA`jPZK+p0ad5 ze>ur2;V9EB)4ET0U#W5jiG!0PbZ=NK<~ZH31eqy04i~g~@B7%Xc^kQb(AeI%XTx?h z5_|DwLwvw`DHQ=Idu{+aZ+8^;EXr3J&Z(JqWT8T9VnQ%;lqghihw+}1vxj0NinJ!Gm4YBTQ+Jr$m+=wov1==}2 zQF>L#J_dkYly?DMayHFDA!kifYOK=HT?`ndNvl*ZtufNHFb`@HoGpoAO)0TtRe_i& zmTWDOtd;M6rIkvXOpwARx7HfjX;?0u=qR-$o7kO#fG;)DoXjJwn&m}EL!E%(I4U%d zNkY}+U62@GIOE))H9aaqT48#Fu@gpC0A#$-BI1dndbPJ-5?;K=c18?yykNYd*L-^< zGbr<17QsYd5?V$vh-XAH2xr7HNMrOYc4oNAJ-|8;Ie-49LUhmaT?AkM0*-4e1KK^d zg<+rQ;4L;J6;gPT$y9{DhZpl2>PB%mf4q+J`NAe1D-53>Y6y$M zDZw8W(Jaf401eX;j=aHb-|EDE+!#7%JR=;^9uac|{YVEqh&^n(=jbKm1F5$||H$_e znmtUgjqxJ=hS;@-a4qqG;u|M`j;PcxCTldn=q?`_0e#)xOh_L$6$Va}K$Aym=CubXO{f*6z*EQru^>dq z1GQl2ogC?tGQH#@;RLu8v@)Xr(j%06d^?^F$V|R|D>3|2qsfmPDWS=mG4V7zjp`o1iv-}eZ8PL zi4al{rlc}p$Hxnf>z4;fzW8mG1xo!9+{6#(l<`Ga;WLuV#%=i%#yYTKjIqBwE2;`{rBwvN`j&!@LvjR1-$63gAn=2U zpTH5_Alnd^($3_0s^^Um^1|mB<=>69@5mK#tUw%efH?5~Pj1P7!NJ+dOdMct^eI>w^-d>)ih_d= z%SCj`l=RX<;XwzfwD}Dr$}DFv(4Qp?f-cu<#TNL$9hC|X@yrErxOykzN}bt!YhmZJ zPGlg*KcN_@_F;$Hs=a2YfQMQP$_mV~Dsi*83MpboZR+hWjbbZblLQ1*-){i|uX&Y) zohza1oHZwn59@!_ZyZ1aJUhkur90g#t|2`dBIHOP-S_?hN4oYoaSvAPiU$}U(N(uQ z2-Y3C#59hqheWca=b)jN{isIT6Yid1R$cQu$Zob#H!r>SAE%UG4JvKDfq1Y1jZVS8 zi-$14NXWs|`M2@u_#f%_pRu7bCf_TF%BRweUY-X<$#2*TLYiy8Cmfnfh)q(9Xe5vb zt(t2MM5JpLF6@&)cMS6+>Mn@J*ShAIRL9AV7M7b|uUF99s1ktwxKP5t0>(9F7o!3w zweTWi4-7Cr*+wzM@?|`6#EMk(vKR>K19tPaDOUrwwVYRUXSp~9jsV>$Tx8PG`uw(y znXej~)lUYDLoC)2f*m!q2Wn4FoJP263kwg!(cF?` z&*S)2DikUz7k_HcouI!Y3 z_~n;QfqqI!Bb3h5hK zT&`p|Lwv@E@5IEb-Q+28h&NnX>U;cyBr}yTS0ciS(j|XT-V&7kojVX(Iv})vJJf*C zsu?@{wF}nJ*v<*K6!u@h$^b(RGlNe5V_CbB*I*#1r10JVCR1?8H$)uS{@cz(I91Dk zh8i+3)O-`|n-CF(Kd$%PFK4D^Uf6VX3h;l-*k<%+WmXD|MFYqTHb&z1tOl8_Hp{xD{qy$E&D2H#_v{ZBcq6}|} z?c8UeiKk#w@Y{2pG~zj@;gbGRkzHEW+ILzgW*5{m1BD*!J26f|&gUu_{}*Oe|6OGs z*-IkLDxKqrHu=*=IYrG2Fz^IlpO$J6?F?gQwBEeaZY2K~^DrX6F4|$dl~CWp zZY>hO$so1HN+ttB&PwII^j$$fQ12EL_=$Ps^O-KAVm$}xbZ~aPPW>3?VsRArZGXIL z`w;x|nf&33i#;1*=hK4$q6?xP{0?Sk??Ku6O#@O0X<%7eNqRG*yO2Ov$00}Re79&F|DOAARBy=l8}cO{YF z$2^FL$M|5{8)LY+@k{ezrmm_T)*DD{lWzj^v8Hd7p#&jzm;%r{-yg8jvpM$A+D8vC zG8PyD9zG*hK|d%bfJ9x5YDf@TC639n>DFRkWQzIu85MG4G+>r<$lDGCS?cKRF(`UJ+ZNzpDe30LyW#FeVJ0tIK7dc0Sl zTN=MeT&)O;*l}SUZcU*U@0V;T94z?n6O4{E1x)DQa}@e_>+^}K$j}wyw?7XVipLKH zcZ{f%;EtI%q|X={Ci1$&Cz_?Fpbr~fXL$ES(=UV<9wl}NKCc;}9gezKmqRa% z$RwX#YtK_IWpI7xt_`c>k>14lbOHe-Lbxmwr*N8h{r1e^3<`pAHjAv60qPw$F<~Wx zJ{H~L(IdL}phh{V=y4g|0G(P({7uzPJp<`U|nQ)JW z_8A`Vxcy4{x3P$)WblU<(BaJ5bOj2z}Mv=SI|D?QZLw5kXJZ< z!@*0R`g#f(CwHP4C%4}SwQX8(`_lom+jJN1oROG=z0)g^b*cIZ`i9k9BPQ{3@e`;x z339-j&l6PaSnTNOwCU#TTWJ6DV{X}M)pfo~vevuFxY8%7on(>lg)7Sv_UrJrRMFsQYwbAvN?WPkr9*v{U*~Eq z9k-u$MWX$vOJ*M!_lu_7(3*zN?&dDdC+9#2 zDP#k0AH1J`GCA1|fI2{8@HK$X1Ztp~^+PM)BR*m{?6mm@8{CQtRXb1WxW0Cs=?RbmV$p(8vS?c3~K3p3ChCMw@@&xIkD^~5hq=jhv zG9^LtOL&}!@L+~Ed~`|e#4v^|iO|FU5wP2|sSq7{i% zwm-RF9-|L}gUg`w#3Pg|qWB9}6XSi{RYqLMctEbVq+O_2fXzZ>hlqGBwnqiBCMx$7 z*kNDF4xM8>X3F0q+Lh>yYkCa)0Sh;KC!b`QM0A^A>Wsj!W`(cyEyjec!`_bz5 z;#>T}`%(1A_Sxsei-d}6u0j&&`!V*%@>x*n%M@k){M!y`g4pwG^TK>-gi-j%#90w( zUrCoyiX{Hx+YsXvFX3ZAp#X8y@K6#zX;Y^N9UQ%8xmSJ!JI$r*jLi>FdhQCZY2f9V zs+MJwA7pIS6pC6Le%P)^Y1->@Iq@rUW-c=#P;JX1X7)8P(n&C)5p7nxnl#2fQSf@MdOFcJ-avRi#FIO7%OmzN`0f+cWiNRdIJt`FV&ykva_$a zIb~vBoVKx}*|%?L-XF)#v8nUZFIMpD!(W%F@-PuaIEmqV5&OO&g)lN)^wnba7!pTg z^mFQ>L6?UnJOoJYPWATdiD6%cMNW&Vt&=(ADU65q?M6z5LLQY#yvXrV+JtN9HBmm~ z?Vdfeot%pE;O9QmdSTxIu(I>+?#piH9wK*5ktkfIQwa2Wp*6gp8+9_*A2xQy`dRNWj4yENK(R^TU5ZCua9+KN>Fn zBI&}Y#y%8Q`eBIa7j(eAk+bmiTF9BeF$sh?VFpq0}2Zs1voTKPdUA7VQ%9C|(G4>K&Z?8X#`+ zP+nNi2H|H~w|0Q-boV6>r(KK(30+sO6OY4$xy27M>ee>yUjgUS7{yMaw0vq3&;;8= zhHhvV{f4=n(Z;VX8qG5LpNEB#g6DB+PaKKFp}%Z|e8IKa(vIx)A=>J0ikBD658ZJ< zy?4pq&>E5;Rea1A##)z^+#ycRfi>s+dPt~AH7uxhNg=y4LdX*J&b$w6FetqbXRs)Z z3TrSajS6S5DUHh<+NYLhgeXzo?=M8F8I}*B)QCI;yW5<^2|%Q%=%)%$uSNb2w;P$n zX^D7PE;>LssTKAK!hS|tn>ln$Z8uy)nWIGM|r8O_tw3auD zjd9WYANuY1?zx&BSmav+e^=qZUk}>Y7#ses?PYBp|5R{=%0e=LtGdM|ulnN7D#L5@I*B++cn)o{TJ899!ev?e}+Y z*D%{)Nwn-WNdZxL0oIUYy_zy&7MtV+7Qcvk?rznQVLHQ&s+;jf+z7>0Ou{?i2b?Gk zjDKR7Pf<`4CGz2kF=x==J#tHN-{)pv3EH(~gm#ojB9HVoR_&D)eG9WAPDjw(lH^LT zzi~M394qar=ONjD@<0l?7EV1bByf(hw5Py5YhD&x5ySQjT=a!NE-gz$IiZnfDx2`^ zAUpy-_G9?z`O3fJwon*JW}Bn*9MI0+Po(^^et@{ey12|8DG#s*Gm6dYK#S> z8zNW|L_2?_HN00DdKpws&|3w$5sV|5^Tj(bU9p>_`9pg7YdFOV0#R`Xq9XQpQ4s|= z0i$Kj8R?rYF`?hOB+WS`SQTlK@HC%rZO1gE!Xx6+CT5F&H*o4&VOEqh=6u zWZ*S{=6xrogfb1p+GlC7$5*4=Keo^iuxj!6 zpuOzL_jpi!>lZ5@@tUeF4B<}MD!~%jvILo z;{mpv=lvk{A%FQ$T9VUqLm_cwam2Pt&L0Ap2|J!)??Cp#96-&lHNqJi0a!f4iq=zZ zfei9oGAh|5xRi?MrwoK|#^mL5vlRc%@mju2`)>bNbu4A$0avd{7!p71k#FR2}jBEz9xZ zc>MPCc@ISpe2A$aE{w##9F~XL#t{FGNKrZa?xSVH4vLyIfj4!~xoQ|hVX5cxFhnu%^-ULO%kj?CQE6SuRu zYxgZs9vh4sXZa8m=xO@+sU{JsFcKVpJ3()F_87Zw-l&3b(K$gVXc4f5-la3$cV59u zk)LyP9F5O0Q0U`_XD#1R3?}i9cyA>gW)LBxq-O(oz}x4qRHhF`VTvkh+CY-2c>O~h zUl>~h#qnM@bnmXkh=gCWhN4(x_yzBg-_bG8FxaTp*U&Ib3@mA^(!2=l>EGg5#i%6i zoB2Fayn25DP#jOUOM)x?phX;!_hUiQum)%iQ%XW)pt6g~hfA+65WYu3^42$-Mm4oL!^P0 z&RC!5OmAxtGLd63F&jUS44t#}xmy{a!4<@6d0H+<9yD)-#ly4UMUNuX$zE|;hng%z zDcfdL14}PM8PCG>?XD1qchrwV_1lz44JlCbF{W%c{DtB$r&1U`te1W}87lL5f( zX4V|GxAnck)XTD0GpXt3RNSXd?F!s?O|6!v$^Q3fi`aqU(YB~?p7A?gY%!9^yMa*rl1f`#?20D_V|pZO6Ww8wzv@5HgXIBX&P z(#$tVKUfLtiX2Lt9LYNKi%0HqVnwi0RRs$CL3msjs=jU9ecE1Cguy(1F>(EqK1 zi@~h*W}EBeOuB*#7f5Wt@tLBtT3zmKj3RZxJbA|%QPeH^DB@hg6#;(OCRgRV>9iq# zS$^)Gg5US%P!{&9KIk_Yo_uJNoUZ#1GKBg~hWGzJ8ERHXq8Jr7V(|8UIm~EinL>g` zrHpv4B76gomy7@D%5ObS2be33e=oB%_3C$gA$SH8@Z?XeFtF^q!;(_79(sQ))m&VFvG8%#bI`_=S&8hwq1F zu{xkxGN04OUSpBhqmMf*Srh_W*to}X1nNwJc~S|q)x9ypjD{tMh#Jd4@Sk|&G+seZ zj@9Ku&r9A|@dyOKiRBB!G0W>Hkw*yk6T%3Er#_EZrws98MqJb>-wXE>A7>rzd|6Dh zteaA7Q=TJ5q+m{8{I=(M@j${+V*iAw^+a+I71<--9Ki+6Bn1}4>CSa0vnCiNj>h9_54qhGn$STg5CWJ6pkHx_RFo@4_O@QQGC6?Sz zu>>2*Yc>GWNP#T(T5C7DPSky}AU_P}{$;7^km*XE5I3X+_Q_c3w%M0VLlJovns3o4 z{)z6ebFw!y;%hfI6bN|)zjebmgR>T4b}Q`J)}b>rDIFNwKPo0DyJB)jpshgF)|qguvySOfR!Gu-wgv9 z3axX#NcH-H%lRob0|CZ8!um$I3O(MJj0Eq);gYlF&;G)$QH#u_Ol6M(-OedZ@4^qq zdDWo>9?>3{id9HOwNyzJLtL?%?jY{LSC}NTqb7+V_7HQ$C79K#oVNf$_lmf4vopfn z8cEBjUKV6MvB@}QdV{zc_G*&6d?8pWEYOy3ERjAx9I0o20N<3AK$e`3{E@YcCrVB1 zfXL$kk^esg-|wNbKZEaIRR}(vx)_oo(4+^K6gD(i)(4K=F|ix_0R*NN&BvDz>IO5#II3XW;$((?*M$=8S2(@2;kFKl#hh+weCM31RR+Us}AUay->^Rg=FBsH+?wymAl2(|6lCFS1(|g@QqhgF)$i1dSDtXzZ zuo+Byvbqy?r^>J!DQ}k=SbqG3`z$Kwv``IMzsC^^ow$S;hsZ?5oJgcPfW(%gV6Wbj zHmpp-HrFgDY7iHT{R5i#h8Umq2NUXu-0$k6!cYWQeS~Dlm4|1bC)JmUyg@UW{d(c} z_Xswk(na_`_gnr#5m@m58MK)y|LnKK{IjC816Gvnm?VSxh$zH8pn+~WqY!E})~g1g z@6rsU_%9#dDEDlL@MA%nLoRE0Sa>!ze7t?VKsX1KP^k(Gp@>NO8pH9>H2i;(akRLK zAr513gY}dy7=m3vaA-K*t{cmzyM!2!&s4*5pDBin`LJW%LadWXvV)hfA58fJPH3IVgpu` zj?F6!&o0&{aKMVP46G)Z%>mYBtZ;6Aou5ofv8D`w@QmHd8M_m^{nAAb?J%YY4I^{T z*b$hl-QF`{QxouKV@%Z<#;_3{Kv^Ps>hX)^<*R3b;%aEuetI zpWZVIu&HPpYZaG%E%re208S3%=cRu3n8vU7 zexR>A%;0i-etLTVvx^Ks+?5PO5>yV9!_s0_2J2DTnBQOl(Ia0Y{h3nXDUMijLS6>v zL3hAvHnvStQ@T(jp1|FD^gY0*sq7v)k8P~t*2-1g-G*gtfi6L?gH`Sz5pMO81)i%! zQ(hvU-P$#ky|!Fp(CWbKi|!M!Ga48U7AE`4k6&pb$3&v6E!xHeTA(^ z!C9^!1?-@uT4eN5R6{{Wtz&j-#9>g*mQnnpEt;n(kvJw9>Yf2tevP}AR9z%T*`AV0_tln*aP7Z^fOp6L# zqJvg!786^n>Iqg?0vR9cs!F(V<*XO1Gjoqc#kq>&qYy}4*(J!*sz@R{v!EmqF2wWm zQnM)wS6mtr?3okFX!&-)j5Kw@&dE&|Lu5y1_^D&@i!!{ixW4;s;E60r5za)*xy^THyJeS5mfi!aw$MVa z`+rS^{6rZFS&o;iX=+{#296ih>~IUYi{c_*UiVnjk>$XxWi_hDK6_apg_1V$e-~^ z69$fnxHp7YlOC=7GN@0GRH}zM@5_OayyOi4;*+;3m}Zd7{1-lf=Pl6>Kh*1xhj6b1 z{irv5p`SuhtJ4Y<{vDon+0|7^V9W#oV@C4t#*DJFjT7(~>HltN{iUvL|DmqW`oto- zfu5H2Dk_p3gb2G|8ZdhJu9G?`SPWGK@_c0erLZOP#UdCz{&KYlQl9_eYJD1jJ*bHd z3TqD%`ve|5U#ZHxYABgVg9kA;gW*rK6f5nPFZ2b>dh;A5-qtMpp64%DOGzOZ)^Z{) zd|$A6bcs3s$ajJ<#WeB2T;_%Hk=joaELG=O z^FW|}Y1)+O+-EojPaH3zT28Z<{t%d^!7C>bGDQ>+PZkYt7h7P59sLSiF24LJl42CD zV8oQF*X^q@FR@4i-wqN#&%t<@PrZ*>;`s1PX<8lpi&hqu)=Hg{xgd=>HUV8EPxF-Jn>8CM>|-UY zsI3PN;!`|=mjR5<5BsjEhR7=nb9r%Q1IiZS@B)|&4ax>gZWe_=lwMdD3aCJvXWnoy zXf2(>DjeSZO-a^^q*f$i)K+_Lg{{U>hI4qV+R8Kwg^Xc0kE@U>{sY(b-l8bFL@Uyo ziRqUq!QfG5m*7=p74))+mgrNLNsq||_rqQ;lVvR%s+x3m#i}}DJHa#Q>M zVb^QY^OtY{p)m#qlHlJBq<=yqXY2Am4#26vfCI;^s3lO~bD;YpAjt;7n24_Ji;umj za5O4(W}8x%|;nqSK11T@cfR+`mz3sMjf)4C9Upic7$j zXm$+gH!d{jQ?@cAV$9j=9noLLKvA`f9_HgOG5@Xzu!m!F&-eo4FnKg-&exv}kfqQV zm%j@UI(hc@DI%8rp!S~I|DAMhluERS$b~tk;(yYWy)Bjv#NN-VKJSVSI*tj5MiF)G zp)Xon>CcH{YXPY&Yfw=~bWBmttluji{Brb^`%wP7;|gF|#|on+@nW<1)lc^lfJ5c6 z`@jg?6thoPdIwO1Fmg4~XJE{t|$-fv(9cdY2mRjiK zxf!0TVUiYD40M&I|JtJ`=VrL2@``Rx6SQ>A8<+6pO%g}H?kZ@tDDxv z4VX5%bYDR`*t_A8Z|AQ7Mp6aCTB>;6Ik1wK{Qi7BZaKg*%`2HW5<`8EhFNv(6aG#M zm#Oo`O&}=v=uPGsQ8xxbPVmn8oY~@d*#f*we9=N-cjwE{ z38v5emTy3oMZG&UM{RY+(EIBl&x;$eTL>+P6SbR#8g-1WFoTNs0E+7%hOb zD^EDVOKm`Lq##^*K zFR350{4Z%9{QQ^DUKD$9V0;R;hhTj2w!eRDFWJ?^RkoAAHoZQid%ST(E3=dIIK+VQ zq2jx5^mpAazl8WngGTf7^n(vdw=L*H<_9qUh9dK2HYGlMj$M$#u?a~DS_>yklU_58 zW`3)FWTY`v%++}`1x&n8)@!vL6L4yIGt*kh-)86)ZmP*i@DEiw8rWsV?3%oHp49s% zpO}kk=43^=BQ;s9Qm>#6nfr93NK*P?wNj?Wmd8$H-8$ilkUsGkK-<2@LzMMcF!9of zC||1L-PZ?Wm!~yK)(^0ci)m|=N69q(GUL_y>};AhVJX{TGqiSV#%j=b;9(?M(V`Y3 zu4(bmqJhxlNoObUd1>BVQ-W?9#lvIG`+L(PWLF`x5+VXgwng=#f7||I`q}+Fq*`(wrg#$%_ZYt( zEzT`)P&K_M$!;=<_cbfbV^DFd|5|g4h5gsSQfC9iu6W)cbj(}5F?gI=#Pt5|qN*n! zO2p>ql;bnIcV}@b8WTwv7l_JpK{)%wb1Xm{&2RCR`#DKre^ZX?{xI*>K6WpKo5H}% zeRHAYEY@BPxA2LF=LBU~Gz7hL)O`R*@L}6EWuzDNtW=iYyiHNt-k(P0$BE4hxL#$IP z>NkAS-W^Goop_8VJe|3jO;hprKtcO7wLVY6$Vih~Eo0-cy~VW&%8*kK!&vn^pX-Eg zwEYwPn2WQ#+mj3CY!PMM>lpe==Peka?L0Vas9kSun=oyIaBw93X8T3(jIDh4^ie%U zpAD>?&aph6gO-Y~b*?tG>T)@<6|c4^7d+YO8R+GLv-D6L$Ln?4u7nG zFTyUtGG4#H6~kti4d*a@Z8+3~$aS%g@C95*j-$M%A$A9@_y6qFH#y0Tlx>r>%Vo92&% z`LSW~59!sZB!zEnD>5f~7bgr@3sF8D3QUkN2L^-(?nRV4sP~*IiOnjMsZ;Jbu#NeyWBH=K(#Y#~&-cl@ zGD*HEN>f=UVv2F=PLesF<(%c7<>LbHrnXX5DeC68%X3M%r(EOz03G#Jp$;Pcl`Xte zngB(rAVd@)nbaN#8xJ~SP#cIV3Qx}F0L7JvE1n$RH-fY;>454^#g&FD5>3WHBC^lv z!25_)8~H-X<@bWAAumr#Gc2`l)%;ci2VApV$HsPOJqv~GsZIp{lU&5%HHPZN??rhLugcfs8My9SK8hG zn$%d}M-D8DwTW^YafwdqJb%`FrL@HtiU!Mg&Ckl|N%JNY4O~*zHp=NK^Q8%^F9m7| z^XQ}vK~mOA%32xo{BiRZEgDM>bCq)^@;O;W92wp5StPTONZQ@v{r%^m5j-_&R>CRG zI5-0x>w$F3q>T=sjev;d=;r}=D-52i*yRC%d;A`o8c5FpU0!FSu^U zX1P(u84I!U_!G0d2QF$So`fhM>xSfbM zj{UVnCJ5IBH8G4zFY!&1MvdKV|nK<9dR_S zA3|edcWvF?P6mLt~ynR>ea{Gc-gKxb%zjmIoy6Zpn+I6=FxGxA&w+=i;hik z+AbezV>R5Zji7_KJX@uP*>Rh$F6V2tMcQL{+-_%U6~-9vE@g3DE?YH+&o&nb*w@g9 zdRt}tUokn2MBMoEclmMQF1*!;NNL_FiC>CC#@zU)rxY%{6^B&t^42fOVFIv6-ZhTy zHmUvP+?$patV&|d%hkM7t(!6|rrfEnZqi#>a!xD^B^7h=*kR_O$OmULreP2;Lc~mS%~IuTMxE8-)P=Me z4FFqotrKkfiuKD3yQ+W;`4eX~GaP(PY^AnIPGGr=_nin?T|!iMv#9)!PSaNN($|wL zm929`D9B`kI^;4z2pHtBWhJh;dzmrGGF}#TbJ-;sPseOaWKy*xlC*cZSaM?-p_@&J zxkzog$IATJ^_=78gk8_L^9En0_-SV0!%sYGYF(Brw*x)#lElASG$_-;{bB?pdeIb& zmVMeAGT}jnU8%6l$)g_4GmjqWrftb%@NOHCSrnOgCQGHo@lLlomaNKhiD>tN29X%@S&iN6ZsUMN8M5g}=If_rK-I#n>)%Qe$+}cE zbrbnt=YTF!Tb}b`nJgdbF;xk!DcfW`RM&I2_T0=AF`;^ETY6WE$*&BjzPS}Jl<*w#J^BHio7<31t&%B|_Idz4x! z6}G(-k&9*|Ap;SzCS7#R6VM-4EIhnLlFTSSM$qm>SgeX!6e#sf>FHKYg%)wTPMQx% z$~?Y#^ucS1vNAuo5?2oKi>}hET@a~>UoaV9rmxXXO_i|MW-S$T z!@O9LX;QF`-?@5_U7q*hyN2*qi4 z{o~YHGas4^+-eBw zsk;B9fx}QfMT$*Awhcw8jdQ)j!z*%8r%Q1NTFw~xA+Jcl=ZW5Ot;Q|qV7a0JkS5hejutiY%0Vh2(s0t7HrJ)c#o>quXqhfCDRB#c96cD<~h8J+PHFGJ7nEO zp7<_^08BVnw_H*#RGqm8CY>jtE%E&JIN>T~y5vkzo(THv75&f7=g_{S!1VX}mU_wQ z&^W`s@iJDteVg-0CCQ6LELIfB5UY;VMJfcnN5LG${#UE@96~qXB@cdDRsBl<#;Lz* zfAj@i)~Q1~pX=ZG=nljCAGLULB3_qSDa`@s4bA#BEbFvQd)sQsXP1K7Vh(5S<1R`$ zfkd0|=}tz&pbzQTi3-F*BU02bkeiF|To}?U1dHh@U!p`nwzuob0%UU3z#?ZpxPGu2 zV4|OITv-g5vVdtV(&?gT-0Ar8BZIakc1Og#CGMHu3Pn_OmYSk+VVC^PqS66dJ4hUF zJ$B&6y>FfLd(YH?nEK)?GptBz`l55mtK9aD;jO0`Gq55bd5@9T*S zhC%0b*yJe-z=tHY&`sReRbV)F)11n=B5OyU|6&*9zSA61dMRn@8I3ejp=)2G2Tay1 z=nn+x8O`00ocs$64wPi{`Amb+G+v1jy36%xfEe8@ zZ#jmosK>5B8_h)P-i$SUmjz8^1?(Qm3D;5pMU;Pjf>|ouRCx^*A&{ij8aH?OeEF0z zPz+$RkTK2OR6g>F{-SfU3EE#M8)t${m5HG-V=vMEJP}Wb(i-LHn?J%KGP#2SzxDQsild&0AG{=YyR_1K9 z-m(B%m-1R@f^}xM_U>;`|L^lujT!s%=XrX#KI8>IEAje`A4=M+NoJzqcZ^ZtL!Owj z!ub|(0#f*KXI*Zj2;vKShuB>$tSXhderyo0zZP6jq#FGQWc{Wr3*?HWgugDP>IU!i zz7cLutFIOH1oOt278!wQchw2z$2Q)$@FSSJp^>7=jf_#u5S@w(;mQPC>b^|*`fTKhGMU>b=Pfc8|Kc_cUy%wMd zu6#^Y2r~CWgEcwc^6;wGWE)}tB#-8Dlqsu6WLuOtUiFIxZWaln*3OVrdc1<&?Kvmm zT^-!ahQYxc@471yOZk*2Y^?o7xg&SK20xnn5xWL)pD-)a)JDzoVI9+|M&0#5E7uHe zB|hBfWoFkTEz!A9D=F6oPR_eR>?fDbzPW#lB1|iWoU=oe)nkgB1ZeFb(e;5=BW%K=QpMAxNG}y)+F*&vqhj*BN%JZ$lB#3Y|zo#^_mT%EyiMv@a&Lr9GccbwMTN= zWw*j@51_Z`-Hf}|f^$MzwS5A4Q1N(Dgf5jJ_UTQ@uNBSq`AtEu)zSAsk5PHc0b${( z*QRvUnk@%Z47xc#&-piEYbF9^!b>?#s;GWpabz!?W*yP<`@ehwWO>2`VIgs!MON z8d}6<4^f_QeJX3mPXC0u9G=3wGD}64MKUhLAU3#yY0XO|CTIk3&I}HZ-f4(739&|| zT99zTOEzFaYtazyLZXzRyBL{<^$C+)hQC;c9<86(CkkbFtc5N+oY|tZeuDwzbhbbc zEKv~;3P{ZtY~aujWSWI3ztFpaC4`$R=VJQFBQBYK4ZMNYV_lMQMcsej|fT7@*_iipekL8=CRL5-KxD#_C>@ z%4wBRIcAk!3vCNoBeVPfZp3YK%4QXNCtrInlIkw@d~R&K&SjS^NZK7Wsm zHNU)PI}WC9y5Fz^mYwK91pM5wvv+|f&rc5s&96h>yUeZvy-588`|++<^3AWxwqG*Y z0;?ws&9Bn7UsBn9D<}5NuiD3V?x4+k*?vAW0exd_G`Dy@90C6Q0#*<5pdT!~=6m|o zw{wfejtjLgl4_0lmML#k+B!w^zWrQA4G($G+wlvFRC z*lp|MBydAxDb@J~6UBzHVj-_2XIV06>i#-C<)MJnEJCqv(KH`_Z`QXT|3>%pSZ&z< zIpY^*KbsWmvpE=WNw+~yalPCm)0j_`fDfrDM#E|zY8dYCOx}+h?hnY>pv*vOFlx}F zVQ$`xdLFq<$*(vpcHQ8{k=snPq0YkkrM*6F;E_VR#hsf=YxdmSc5QpX<(FSmbv1t{ ze5K3t7Y}({Zs2i6gg2+*ATwBv*4n$x#pk#RW#YN{`LP-A*3K>M#I@BO@5b`RhNrj3 z_O$n>ZE?YmE8U}3@jZESZ~2YH7z0U3q^^gl6bT9CO5J92aovfTFol`9RHyU?EB zS}ZYJh|PmtM#cPBGu6(l`mAe;+cX}dl_&XNp%@m9YwRXwRDy|aVqJpOa)dKZ^IS&! zOJ}##f;Y<&sd=S0eJsbb@jAbMGwbZol7+j_v5UsLa#ik0qlZa+5R3!ASxNEJjbci! ziu2fwHyInfx;GTMl`L7MU4T15Rx{Irc&Ajmyl7RbL=9>M_fzoi=q2Z~R98RBaep>e zOeuJD^um_}F_X!rBu}dQ5BFEqjdIz+-<#_ZIVy1<8M1&t#wY zFUIV1X+S|os1{^2+_oG=snWX1=rxUl@+$mV=Cxw;%F@afe~oY=8sD}>ERl_O1vZJ#@SAZ@4AnybeX@cT5<#)y;J+~QFrnSTMM10ADL zRY*K1-THp2ks6f&X|l4j!M&~RU0rWbL2{dBj6c+A{>5qE_ZhEw&(`Ae7{uZ$I9r3q z#U$3L-k(sN@i0-ezpu*fbH?gq6EkrLfOVp*s|?SYCkF^G7DHxupfF^3V4uqJhKb95 z;QH+ho9Zw*f-*p%%lU=qYsst=Ys(HhOb%w#`=VG;qqkK(Aw)_N69=T}<;sz+#$B$j9&?Gx=$W=Y{ zj*4~4o+rM`c8kzV3C3hNNr)qq+otbuC)N{YQQrKuVO=Rxw1uxgso2XDd}Hw#jWTP; zygZE|QDi)nz?&uwD(bKno=+Ka63H$q#T|++<)uVuaOjwkWk@nB=X3=-lu;Vk;M;6< zg?v|qa#CpABSdIbc15Mei!nEDDBb(Yu>Hm=(P3c0{6Yy6u#RhSp747?#8YCXGtDu zpt1s-&-IEW%D~G(kNZMr+>e?YH^x$R8>1Ld`Zys>UKQ%s2baMdF_(>;>emT4ryXyk z+Q4wR&+D9-w1MgrpbyMOh6Nf6kmvS8%5YH$mky^~Q0?={WF)Cr@X=B!FAjPqu^qGO znUv#6Ju&lz%W@UvHKwNfn-fbrJEo06i}Fb;-a+s)c+}G*7I0#{t1KqQyhyIc#z6ZC#9)=1aYgDk1I?6}O2Vox9Se>%#itd}lJ~xuQ8yjAy=K325G6NJM9s z%V#K<7V#oUrZgF3QKROMbb=f^o!w`>;rUtj^9W41=D>bAMdUd1S?;e&&*#b?C3A}w ztk`FY`(?AXhXm)Sxk^XOwSjjr^wdN1GbmpZ&PSO1R*$@QB=hRs#PJjT&K6gm2r40_ zg{K`&{3)m2DbHZfLphGG9`)ZjVFh;2bWLWJbNL6W zGQgaxaU9ZrU~GWZ0<@yKsrSnrTE&AYapP3eD1Qe`-_sSmVclC}sD7ERMzfGKSz5?- z1`%Y02bz%vNJ~HSsNSXz{#cgK8{-etj`J8zM{d4oxibIhD+2eofD`G{?2>X|6SnJH z+18h3o4M_z->*3XuhZuyfDSA{sp9y%5Xo(**^ocpD4PuI1RGd$1yrhTaD{9pJJ7XO zDI05JdTA6T%MHFs2!2%o4Q4sspjBy!M)V9#<%CgO(-AWx_7CpKPCxGe#s0Hikh-V- z*vA_#x(l1#E&Xk8Yj3wrPu+&P?icYa!>!u;ip?#2-#uGehA*F=((IV9 zF_k4;OFAJ)_e#?rUh`!7sMKHNJutG3=db5oI*s532lnC9&XLr8KOT~A_29RM8;3X= zJ{W77jlgJ@>Fe?bvxh3H;w%M*|59-iaKbQPD@3ACkD^bHsnHIw(+;_9#p>#;$1Yop zd0h^Oxi~ywZrl=f-XeB70s1+n@$*j*TscF|FO&tZr2M1)0#aBE3MT|Z50vKq zpaiGmWsLpthbV9AnsKON2QjGq_>(Sp0s}p!?Gis2R#$}hUVmr{MQTMY0&iGnHXpgV zaIf)M6v*0yCigHHs7FqrtWMh6$6)wX4ay)r*V>NOx6{qly^;1m%ISAQ=?yI*!+~~% z`?pZoRas6@C<-%N)w6~(s!rMQ*xdO$sYX;YAemvB$o)P{iX<%cGNr~HDn?DyPQ_W$ z8^_&=0+I4O_bsSLhrzR{440MKwex-*`NeY$>j2{8N!VY&9Qm|M%^dh##Ysg?`*l%kgLto0y#VDEEId69|~Rg z1(nb;RI3a7QN1d>W(!^T6}+(!ol8Okre2{m$Ug!4(VEIdiEe%-OX%W8(JOY zsaNn{7Uy|>NN<7YS=`PrV&OT>=a-YW6R*5~%@7^F>L+its46<8K@xm$mW1@!4O&^Z ze$a=-)nZaB_dzx8D!P!L^3nT2l|$_z`uW@tB(KsjwfNJ6gno%63=9`=AIReTPJJ;n zo+amt%;94w#-I&FUl1=&H#Y49ES6P%zg|@Qe=h6Ba=BP*Oc$7|?7ml&$l#B-Uo6)pnF?J!G<^ zJJXB*d@((ki%#W?NU!k5s>F=rkE@#1^&M=H0e@IGzvHTWkBUC}m}ZOPefw}EVib{s z$vt)%U%`f0E&7X#>;0uHLkKe*?h!aeA8sY)u;uOf1K%AfzU#c&jW*>MZJKYp^j{2m z2F=bw56{;+p2+2*n?~g4I;VqA@BHDEt=G0{)Z9jpQf`n^Q>}~wwI*|U_k_a*7+OVs zNjJc|HQ_i3jCm~-Pj|4KZBT$PTKB7-5UGmZAiF;Bwb-uRF$Q z;z;^!a_za__RX3i+By66{*PlYrM2<*(QnF;>NoA3^8eND%DURPSSp!1JAAMH{}=2i zN6p$rMIG&Hw%aZP&Jr7$kXOnBN|O9MRju@;$zG!jlt0o}$dsu5Mo#!6+4T9?k0e(mvn_!RxWdxVd_JuHUgETpvNHS4DU z-`Vv!`x|G0Zm&D!0P>IJcy`>yTQ&h5PF83d$97cxF=e4%qND5*4{UMfDf|8M1bb_b z(Rh$JOOKU!X2ATduE69SAJLrUOK%v#i(;gpKoJJ@vvQ$`T78(l` zMb6S=Ieug?_d3B@NDon3&YJUP%*k-hZJdjUVz`8*@F*QyFa4#Ph_r;LW?vQ_1CMf?<)3UCEy!J6T5DAE|Jkg)T58^j8*U%IZQuv z#xbp+{*PRHFG_9axkcnqu}XD7SG!Gg0GJk`utwt{ zec=SKpS$%unJvWOK*5q{?%b_ET%2X3fcL_0;*!4SUE?yqDyRjm^6%F&4SO&2jzNnX z%)q~achxpy-LZZ@Vwb_Lq?|q_RT)6j%~#Oea*%Z|j)fLMaz(X0?@22B(F3z%!E3~r z_0{4%B-V#!N{=cAZq|<6#j#x%$eQK%LtNhU^d(+Goc{P9J?9#`^)@okOT**I* zN9ngmlRO|aFSDvgv4X!~3J2DH2-ZYsbWL*FR*am#yHP(Jj=T7kUGgfA9Y!Q*^^jL6 z)heHsY1SKmqzj1qAwE3D_iy|!m;*s8*e!?`YYf;eO?Sz@5{Uhw6UfcpDooQ@3t52s zLXiT;bpg2M_`0f2RvS$7*kRZ@Ixtz11gqa(WaC>@WVOF+g0vcK#==pmX;jDE#p3Z@ zucddv?eTRpSP8TzrWBe^3=-%cKRvY0PI!Ks?ZOd6&u=utIJ8%tz7Wi_z#N<@vF7BkYm?P1Z#r$CmDQbB0fYCo(6+> zEnxKPJ9rKf)-f50$t66bqvTiA%t75?iz?4b#Tc5M$bNJ~FcQuS5L(a>Xjfh$j##IL zem^9P>1!-)p9u^B;Rc}D1voirL+c|Cqb)out)#eC{PNLot8dw;RDTK&_(JKe?BwWp?+)xk;urXn2<#df!Bq$+i=H=Fv%x z#PM?6l|+z7Up=68d!E`md8g;s-8CO4321^&c1r-laG3x0^u@091FOwt-Lmg@-r?^F zRO!7B)bdaWmz(`D>*-=hB0gf*Lq%c@FF(2d`}5)!jG_Nx@YDF1H6Jy$6;Ls|9)p|d zYRp1LW%;><5-EMjb`v!WdrQ^LPCaLS!+N}SG&kDX||3hlap^dwdoQl=ElIxBJFpQR&O06T7zpi~HYuI?fI$7D1u9!AR zCw$wU{wk)hV{G41C$;j8HIAe*p zi(08d>Zr;Bqb(Ey+dd5D0#`<1@Tgt>afRO95VAUigMY*oI21&C1y52%O$*0T<8acnCPw1siMo@)Jzb98>7V%5%eQmxgOaUPQM{6W-vCKa zixplN!>g=8Ik5*;!p1PM!B)S*H_1Hnj!AEHHO-UE%}(z9sm=8+|5cJQ#3lqtx%lWA zum?@%TFJx6T5j#DtRTwq;6!_RNT#`~+-Hv}4dm+Rod7CY@7&C>^~dqycyOjX+qtGL zkWq^p3fI-IlBeiqGaGZQ^kLjf7P9!-9cbx?|1s4&9I&`N5g@lQbUO9dqp(PLuR*sW zp(8jwArAdxL}zkES9T4U;GPzNXI7nKp{iq~J8gi)Ksp@Twflf~{J@Jp5P-YzZ-7+4 z#RT@Ms~Y8Hg^j7Qb5 zteE#R3$qpEz1ODK$GOO7kPTUx33%A0%ps z){pxNbmb%2Z3gmKu-~#4xrcmyQAS^2O%F2B4mbHI#sO1r7_BKZ?B*IWy(pVJKizP?Hrw(ZwQ9C`T-B|n+!{V3*TcU3XPp# zjaPCz%-AvT@K|-`tR^H2TunZH1p6Ic-c$1}{ecQ=Nge1H$2D4M4eN92m(WIO`fQmheyJHrcZ4Lw$la7hkYleaYEHZp4Oxfrob)Fz z*ki}I{(+MJe*X6*vYzqK#N2N!w1f--!t}ptAr&V>J7>fH2&hsrHMV#9uQat(^$T~@ zW2}F&f0+$g5Rc3-7=@Nu*SG6`moI{mqz4S27=bo`A%<-eb=6;2H=!IE2}Wax6l+|K zfULBxEG22#uz>=pTUBU#0QV)IKfS!-P9L4+`!c_BGq-2#8fkA0zfs_xdCwgmxl=se zPtgbhKkC8!VfXxS74KX?R5_W!={!my5cW3NIHSx%dCLx7LDm_T_A=_@_eF`utZ$L! zIC;?+AQfVhL6;7AgybeJXxJ=Dg{!N_}DA^U;iG$5Sb6d-F zxTLt5DTA~98GplEE&JVVaj|gEWaMsCYfF00G4;3+ONM7nYM=d)U!Rao%boqh#A8{? zfyo4n0-jvK*G5zEZDU;3!%H`}M7>h86cKvphEpYUYY%+yD&-uuTDG9Cm1v<&Q$2nB zCcp7*j`1y{EXcM>0o6$u(7h03f`Ni{+6ddrPv8I}3InFQO`RpWg zDzB}S9Zun=7sR!1lDV@@)B_nZ<8aYmkVQ=VCA$+kb3PnnD)2TjH%Yug?4i#6>=VO=CK|2#=0P>L3*2y?4e zWpw6^Q%x%&NXV=-BvZ3o3lUf8uG&}UtlQ`N$SS(xPGjSZvafuJ<)wqyp07Iag(oo3 z5d55JNe)ALjFQJ-wHaZD$G9`x=_Z|j7nGJz@Ocvj0{fLgquF-Q z@cyIvsI;9ee)G5LI;r*I>;7#HvX&k~$gWVdU*{;%iOJnQY{;(@#~J2n%|EoGj(2P? z+j7ojXm*+(m}&gcQrwoC7o~Lv@X$`Fyg98e(sJCZEblDG_=11&4ws$1bC-zjVo7TnC{ zzYzPtbP{9itKV{)ITuj3hy1x#05X=s#re{s^TmyD-6n=PqmwkR4+}smjyXkaiCw!F zMlU)uB5a=(*lJCCl2oO7Wa#-8?NvCiqcX0Iqm8-G9-8`~Wd1($^uUn) zpgI$pk1RK5^8R|pO}jdZpvbZ?>St5+aKRl5XmTg8s^41R7)zz|sNrIYWauVx!lrmB zrNYeG)3(OiMeQKjI!hrLmLj{+;uIM#^V+j?c9m70KY;gge&wX0;UJ>xRa~-zehXvT zR%Q#@)cO$g=VXq-`gMWpiuK`%$74XhH{1Xan8@;APwkr_=3|k`ZsNrhkv_L>e{}Ub zzgSY7-eESLg|hqS;b0T)p?r)ckIMky>^&Obtl!R%>`eNXuZ9$~ObcKODaom78CN)< z5^@RY?;a-RlZ=4$IG}|+!b2ZNj5f?DaY(HAh^hGCNu~;@rLd^!2K^q!{J=ZWl#Gk_ zC)#y_ksIt&lh|gv?3aU#vz~8=_5*hSXByTlq}KpMe%#H@lpGT*sm_$?2mMkL48k4Q zx+afw#OE>k0{NO5!#lm^9=b(# zbPd}s{stc|t|)(A*ZdO1pJ)dqiHsc@LQi-({uoCrlv(NnO$!75ITQh-Noz6zPU{Mm z9#fbZG^MqaPaUKW558=;MRU{(t8qP!YPuZjH3eKM@bv9G&EzWZdLLg6V_LbEo`()`Hl3fD2_PD3N} zf9+zl`^PMQ5sw`^x{X7RzB4m%OS;G*1*@;A*P+*63gRKP5tUmEcSmCr6r;B}r`<9; z@s#EMv?EY=ssT_w&O@77^aXDvPXmQTXa#z8UxQ~wUCSA<^GZToh7UG*jcH&a(C9Nd z4l$;?&j-M?Ek|b2f{p)Waq#~(=RAd!kbjuu4Hm5~{6K3;z`N9A0@mj{jL^<{UYH`- znX^qE|Iq`J-`Adfxc#3#2P-=j&Ae}sGxmLbTOB~`oy-{w9Sn^vOc}iF?XCZVT*M&n z@c)~iU1MZn1{o1UcP-eoVBILLb=uh<;R^SJI&Z-metj~L64lW2X2Bxt6%3=U&NAA2 z-0nPo@#=#xll~AQBp;^megMC*o&ihjk}bN9V%*IX+H7S%PVAc012YRLc`8wnLqnSi zZ&5cJ(>t-tLHaH6YqWmXYPod_c`=u?g}G#U4ju!~@D4JeR>(JC7jB5S9jfS-BqmsH zXoN_(N18(azYDqeyIY+5@5Rin?|592|K;6;EliEA|I@}1s-i28@||4?wCgIdDNy4- zlKvgps%=08LoAA|Y`j>k^)m!4Ph+xJw|u*E`(RAqmf{RW{%tUr6ef~XO#f4Hl&33O zt6(HNhtK(Rw!?fjXIsCo&lfUb&Y1hD&E=>`DqVdv%CxqZ zbAA5+mJL57V7ZqXRZ$nQg44p1E{3 z$B;oV7H?#3EhMXGz1(>7QG}&no?wa5OH#yMm<>_ikAXgq!Il*|%(}calpo(CQ z(q9Dyna0pa7>kbn1Q*6sbHF=Wyg zN$===C>z^#mgyY+R-S2Vr}*dtm;Bh`BK$Z;@j--2Dw3;6D%Aw9^$hG1j}=S0MCEsV zr1=;_z1xq9h*m8n8a^4{Mm0jsc5vULh5&(tKfu0&CbfEjN^a5%b!WwR#bSF!0zIdE z{YFxQeTYvGB;=M;K*<1|BcOu>g9cE>Z;+Jx`UPa?h)m8=NdGA+E5(oo)BQG!j%Xx% zY&u5Q%q*>j6JwMjek(AFF%m+5+DL9Wn2waCRLE&8uvK(YptSt&F>+d!5HI!4`}JN3YCxfRK#w%;oqdr0u`d5i-HZb)^KUh+!L5@9)@WRoJb z{Zw2C)W+8hyd*3lteQ|H=H?eqafwjjD3K&RY+aSYax6>zK}IB?n6`l`6rx0z zCB3O&M?D~fB2q{N9;Hf24Rnzx4Ux%^X+LLh=tz^N$Kbsqfw*XZ{-_b3X*7vy^ z@h`k~Hj~I^@8P{=+V)IBR;IH{TM7rt(Q=0#9jD_V1p2PXAZwX~BTSxaYC73)~ zbP@~rcxrU#kySS-$s5H+B$QiP9M=r|mR0 zski+<9g^GJwX*0Fi-~N`T8=tRaJ_D^NxF>VnF6-wS+dq_%GBp-o#m?3-0N+u6ues3 zuD=lVE2`JQ<9Z_qh1H9urjEz#SgJbaCKr$~_?s)&SjU{~n1qm|ohZrF^;sUPJwgUN zn4dM!xcdBhE77Q^243aFDaxJzIWf5=7IYS=NWeve2x?Ok=nOZ(wlL2Ux4Pd!lN)EI zXMiz2&_h@o0?!YZmy5Z(N}lsXRNk~jNrFNuqF zj}ZaTxs+|Bo(VYzfmOHVDdZt;mIed9D}i34)RN_qLsHhI!bxtDaK}KRu67~KCR#9& zdalG)2hOP>CmK)Z(gcdZIf3ZegxV9_X);5z#pz8jn4^$a3xDs0x^rP{1f9NAwR#K! zjmSo&0Xo|hpsF~`A2u^J_Y><>CBafD1K z%~5m&@vq|@er$g!rs;Pl^?Ywv!C0*yL@I1CF+X1)XM!H?AFAP2mjL@mYm-;vXO5w* zF?PiUd>fmEoRTWXRae}I+9X!XgyClrU1vzA4t1pHsd2*SuhfUm843p9>XZI3I_WN% zZEA6HB^0V(gKc<7$=yh0{8O748)wR*Wkpzz^At_dy~BDQ9_tAw`>Dd)sOWFNM+Kxr ze8O0%7!!mV{tz&NPrC?7M>ohFja|56jel@H4f(7;|Ir1$!cEXO+&GWF{Ghh`c6uh3 zG2GE=x^rWmjXVU*6GtT3*E+J}7dZ0k6*!V38x>bw*CH}KcIq4k8RkvhlB64YFbyTJ z!gfGUU=edk&$~w)={kAY<4-u`n6%3=>ri0TCc_mG{DdbRf+s~WDM}-fB+ZmfLvLI) zx|82ewz{)NYKO>cL-&v9k6>ML$LaO9FkBXfS?o51JK47AyQ<5=q&&?( z*Jis;7|l-53GFIzx=^>IU4RP=V?VI1CXoOMf!U_eHx! z65$R|E{Sj$_Tv*tQD<*{UY4LZXFAX_B!}KD9GL|8@exZ4;{WtonJGMUkpbJQ0J0@A z`>FP=J*MYh6tkTz8mz?|V$dF6Z#odG7LJV_dO!|j!`^=7hI6cfQ+2inrx*Bk zcLzNqaU$7+J$AU8l_NJ9sL=l1Fquk()9X_{x9&X&A^8b$Zqo%*wyl zsuYU17hKDCEAZc?&Bwj;%Hy}RY5V?G|8L)E5z}wh?|+@ug{H{LqbQ<=?egTf<-;i@ z2Khjhi)>OUMD3yyL40eQC{UpwnlLBeua#NOod47|QK@zs5`_s8MlKh-4h8hAqp3Kt zr?WCvbaeD5-)X#azSlfA9YBP>-EQ!NK^aWu(LswL4;oQPa1`QX~K2P5tnlgi;GDt za-V8P+vpXUZ;eFeY;`6udaBblq9ZRQCrFX}(pB&1*UYj^#qd=}p&q^SB!amtPu>z8 zpZea!SgB{abM)qfr}gk=0(npg54fNJVR-$ApC|(|g;tfDpKb6dz~$ptPtxm+oPVSs zGhJ9(kRH899ZJN=`4tbYb6`UECG9Q1vX13(fXMkNc+^{;( z@Xo9*UCkjA6s|&yor8@m6&)`DUoL`(EKD-&OL$W9uu!wX`6G;|vtU~L@PHFde4Z$b zhFYbW1rC1)0!?MNTC{EZ0x0Th;}A&v#04mpFM%&)OR5hmL=-i_FePTyE`9>}Y_%Q$+YBy- z?-nx%M@EiY^cdFt0Q+Y1zGJv7+N^B`7@t9Gl|zm}QxH3bh%jmXA-D}WDfCm5<`q}i zk?RisfE(y{JHR{2-k=XEdbBar@DA;#DZQ18@`ue?~n8UH0#F%kK;APq4+(T2EXs2twx#_TuIwJIONIFK=DK%5n zwZjF58Y3A2jW(U7zbzbfe*4nqWSOdiiRck>8>!N|s~G54)?4wj$2MweGM(s*RaPIN zk3F1srSKUtNC&P2sU~tbJwmR+P{`a+SJ(&GP^Wdh9LU3`?2S9O+X6q>_Ks)cR%?S)XhWjw0e(7BOZY*-*)u%Fb;6yr!xog4d0-uxMx+}C2LGENs&sbS zrkb>j-YlF|gKV!1`{i=aTu^{!i@lME$k`|@&ni=>KZIJUsv|k?BX+U;_e4B zHvsi1LGIn0J;7)kf-ipGWc~pYqC2`WkdAxr|(PGLNI_R~TrtE`#sFV26j z#{P&Kjrs3ttoR1kt`aMoAHLtjK-T&&4 z{`;H*_JN?@wZvgA&jAng&ox@=O5BDKtM}qqH}+t2Z-chls%;cf zsX}mSl?AnV?R^6Y%{lBtk%sqlVsRNuC~Jp1mTXm8zp zKn!JmIE%dd4%F<)sGUNpH&4>-K7f6jt^tmM=tZU8DW9Q**dN33RZ6HO?b8X-Ol4<( z^mg4}WYuSr)h=o!*ne^XQV*nmmfeK~asdoEIbbNt+naY25A=^IzLg%8*vu?u9?e1# z@MhAsNmb83=S2gZzB5x$> zgldwgi9@DA-5xKWCyPUoE2u(Nv7uD52y~+#tUZw0GHV`n_K|3@>*QdOm#2T2#4ACC z6lL-*><9bLAx5nV-F{J5{#a>`aFCKTibhhHVw_->O>4GWx=4j#XN*_HrI#=T^8}BO!43&h$VPYKAa5r@-oT=<^rtl=x3pN?IR+f z9j;JWHh`?T!mxRV<(LV=VswuO&ao;!Mocl#oMeV5ns`h#^bFa)?3;l6<1s{XACxjrS@bTx` zJJKj$u&%cvJ^$LA-W|PXU^G#NVn4&X_X5YDTpy<4DsL~RE#Sh2s$dN!3ICEJlY=Cf zZgj{FYBohHtEnN(a-YbOtrkn&0;?Qp4KORp5DX^NNh)a<24QAFj`20-fzl@&kMJ3B zW%YbokrQgF&)>GKWGI5D3co|Z>i7GVQ3tm_efjQ@{$Gyl`;`fr5;jW4r!)RRSjM_pdkGOe^Pho zl%G>?3M|=&AaNp%a~L%fO!*oVk_0>1*k>z{d9vD&dNv6wynTWeV)qztzI zTQ2VLyLA7Lu9&j6|Nfo-=oImB^0Y@+eARj3Y=z(5QBMKCzzq zv~{Wd{`!d1gQ+4xZ@b**34wX}t3{Ta`woy!i|jb^khS+XnCtt0c>;W~zc%25@Nl$}b}J%hF% zBS*k$2GO2O=JIgmBU#E&%LmdLhLqRFC%}d98Pz@6yHB8zXK-%x7v%z@XvlQkg>qLy?^~mP2dXWVyXDWv=9{0BDwWt@U9bCQ_+uh@FwZc$IM) z7g|ED`OV*?*ncDZTxR&j!sB-ur2o$;@PF{|Ptxr#fKd1sAS}4-(%LF1q+iU%B{jq{ zufMhau;()~9iDAL3-pL0l4Dt}+4|Y~l zGgqGuC!gPXzc&5Y)xsXxQX?TlW874lBe%yZd% z(aG-k@%XL%*LK!^0~0K8GVS={i@WUqEn88x`?{z7WTzPT)^P#>f!5IC(j%xkbW(`Q zWj&y~Akb@2OO;oJrl|Jhg|NhpE9_slC<5PPwY5tlx-uQG6AO=nu(qDb|8YAzypXqr zdLxskTcq?^4ti5C-f1e-?|diMus3<@1&aD!#VgHV=)ZzMAUL%f%LHGRH0Biit~~>z zu@^-pEd73JsVsnw9%7^m?FM@G6@*@*JJkaX$yo+!Y&X`!F~KnLg(Ns^1WI3#a5^ky zMe{2ed+DTo=uv?H!|J({0efcfIn8$Av zskqDnYFm(BnuTo3^un{}Ik==UfMH~IW+e8A6lIwMKZjm_9BjhVrfXUg(+}m~2kF^+ zp6sJ3(U>h)j$s)#XdhrLsE(9$j;WToRPr`ZdR5EE?EEo)9q9_J>I^D+4Az_{Ms9(? zX%5|IZB&tTMFU;we_Xc1Gzo3>C7hbe6yUXDxG*9L15lhxlZ+}j7xbCInr(0rici(2 zN|8NxI~9J+{}veI-=`c+Oli&jYXwI2)84W3I~=`zL-7BG-@mwozrgR`@#rj?y}%4f z0H{!T9ludIO-YTz7IH5Rl0w0B5w6gjc9Ap!v3ZEKDFB!+2#KuaPanuX!B_J8*YHOS zWn<%?)znqCtIo|2m(MdcUrn0x%d^8fr*GZO$yDZp4}LbS;4VgKiQ(f#Dld zllLmw-Q4r`4GO1sKgV`PN2N^GNY_6?fG<||6h&UYhpZ=z*6+wITN!_cx-@fovo{#w zgAvv7y?kQG|IF>glDe^24oSBR-HI+8cQ`OVQu2;zqUy};T<0s~-`F^Z0M}`4ttE+h z2!MiGjg1hQ&%bV618fEI!G=9<%(tA@Hsw>Lc1ux1E!o^Q!lg<nr;Kyi|U{ zMVm9L#AX9S!TXM0)49mC<2qDr^3#hGDrLmBp~XFR!B?4SP&V``!+gYEX&UC7!4?mW zeOy*=BRlApJw!kPfnMRrDPQFaaz16@OW+M!F?-?br-$MjfLHhzT}^}y{rza600qN> zQ4|rfT?C_hn1az*5@l&cXh_BPMEqXX&VKa1<)wdWW;9oi2oP4ax-AVf!Mb#}!fAMr3FC@Sc-m^eQt3`o`Y8QCgE^hj%m^sa~F zEZvlgm)sT(?-?}v@F$?#1@~EBuyxJXv8havy%&c5iQt6T4w5~)82Xh>dSrW^9Xo3B zl~ZB+<68tg*FG;q;in*Uh!g9my7xt(Mw~q+Vpwtq>$7UG11vi7)$fG1;wbCndW-+?Xu9iu zSySuJh{QZZNB#O`w95^IzJ$3cxHNlN!lc#^Tscg%*Xk(7sd8KfLT)R4LpF)u8OS*L zEd{NpnZNDK{0K3T`UP$kZ3)9VN`wrOh13T%<{bw4Yq;L?Hx7t`^zK~MVgW~4`kL>F zr!Q-2ABw_JI`YsIqCI-BLCMjeEQLm+6<4JyeWdsq*1ZY+gZOtk!>KAkG$fEj>X_R>UeP$y#*C zI(2!PHhYAVQXKVT$&fkVW>Gb4xCgIqc&tA~U)=kfIIiZ}**M2HrkH{M%OKtVt9JfP zy8LfcB`WZIqYB-7%6X5tg&7#IVTP3%1d&VEsa$AKArM@mRHzKrGWkzJi_^X?m-4zF z07MvG&q5eP5;Q)4EINF}cYgC|3VT)n3b?xbXgS)~_W5|cLGm?Puop9+g+jUGM73{7 zwr}wl#xVk=0e2gbGk5h~J_Opjgm{~|a}GoY-@d$#S(iR-8H~|(dX;fa&(%y_(A*^4 zVW}qiPJ=Q!+~}80f!(7Tv__CH)svevmt2f8aq24$fWf6j;8*Yyf(X!BaYhSoUc1$; z+~B~8Ht(6P2JlaGANJ|QXIcoXL_Xn(r+n!cznFP!C!QL7hP6eZ-cmg&O;6T4LnR1Bwbw5JrfR-7` z^YwEeWPW0&8cp^(6jvlUNd z6YP=A(q%9+>qlnYf9H89shYA)K)?MkR=>*aC{**~KQk)>^V-L}{f1wahXrL;3fTXZ8^zw5LOyI2J z71Uyj&RsnA2*ZHzj!>f6(#Gar))rJ0HRu8)01!lM3E_SW+r{6b9eg%`qB1-MtVNR_ z5+0*)%<8qwGhuOO6o_AVvA_D&H=Z5s?eO@A%p?d{tGCd!Qwq{hB@C2Nnn*1!VAQ1R zZ&mjoL6G+OcbWtF#v#%FRb~B~^i%xz96+>|x;lAii4r-=jI(^w>+hSw!~$`mu$V5{ zW%2eJ=k(1=d7pSXVoXH5e>&TwYDxTtTTk}5nL4%DJ+wYCwhK+p8 z!*vd6lf;BbrC+mWorDfd`*e4ZBC{ycVp!9b2rM z*4}z|&q5QsL)@g$>QSr?P)f-eANyG6R5ik+FscD%{h$^RP0?Mvzt%*UPlVeu-(X4l zPJ{&i2`utXHja7@|9&V_7?)Z9iOdt9vI23KDBz~3T?hd}&AN%K3`jgcm?xYgW)wg= zMUG+4UBz^nr?Y7N)4b682cGgnR6HUS;Q*n_<^FdkrSoQUTgT_ec^{P?`Z%kK#E9N% zZ<*UNb_JKK)dmchjir*M5{5{g+6$V57Xl$7gJ7%kV7DFJj@~%ed@Z!b6@+6+U({i4 z)g3+pEGvdcH>t#(lmSD`vO;!Ak*+QHh69ubW5O$tV3$HAlUj(Ga|NlOU7RMHkmnSZ zAH753bO2{`VVH-wWV2C-UTx7{(lF6wbI{Yrm)O+;`Eg8#e%S%ivQ=>8*gi;EfOB0Q za<;@pQ__?Y;@h5B+HAkpBhLKE9tbH$5ndfOF{5eP<}g#40uAk!ez>2TQahbi5!z|X z$`EX3PBt5&>EsJp6LHq(eRL$%t_9*o$ZW0~a#+o1%|`{)eZxEfc4Zqy@aUMYq8`Yh z2O#xC-yrz$bTN4d!|!3H+EQ6V{q#M1Qd&Y;-pucD4kK=Nj84JyW?eqT5y`qBW{wcE zI%_mvj66Vu3B9gmPbpJHjn~MHBaJ`fOE3tt)tcYjfCJFcFwl|<#u5p2410guv*yyC z?$QT9MFe+%`@`QBmUbe$?HW!l^g(G(8w)8d~ zj&_U~HeNlCo3!S_onZjrOPUC%6CW+o7{)zcyn%N=cU6q`W*S*MVY>Ac&D-YF2h=F@vOtOHFM+9 zQMym_-W$~na*L&548e{?R2y~bdOTLIs=_A1$vLs|0$6D9e$#>>2~4#6JlNaf<2GW6 z2A>RttPKwhhBVK%9j}JA$Q{7au0(=OB z+g#jd0Z&a&r8-)<1ZKYeFcVuS+6lV!mardrb%D zHiWB81F2TG<-6Inno+Mof+}q{i4cIu54R&Tp*~o~k;jmQH_Q8E!H|@yD!ekqbLQN%A1#Poiqs>g}Jyd5KfZ zzF9;6`qWRE(g?Xu|1TE?#oMA=MlF^vd>0G%Dr-8J)!AgO(}f3>fTS#4Nm|9{5I@}o&CoU zwC08+=ORt{(+eP@E#3V}1kk~S$!Kug)suKhr zS{t1lP93=HUK_lv1@9_&fB#;3b%Q!Rf?*z7Ro`A)o0;1XJucJ9St_-$+^G@vLK~e} z^g%f4TXUQI2&z)-OQlz%WBaV~d$Qf=`P%gY@`;Z(TIzUJF_`Sx>X#tve!nrwhc2zitKy#0|9o;$ts49-NjdbKf?SsgE2*tEGaKoMf2AVz0D31>6h%de!ps{`K3$1-vVIA=5T zW;X^U#6h&VU@jk`p}&kjaBoMq0f&oP7fvMT&=WW#St`8&8UdT49i)1{A)P9QmC!_CjdFpM@O+SdRH_PlNlnqr z8e}Syk%u)DmpF1vFP2D(7LdT~gD?U@@+6@0)u-ZyDNZXOr~UxM=Zz=X+5zE#%h~*!PuzcC&m|)*2C}T;{a*#}HXgpOCwmkl6 zkJ(9zDwQ(l`fAR9={MQv*SN8rb-6IZ=2Q_$CiOU?>TyvS>VQ$aH3;=FTY_;2-Q@@G z5J#SA#%?RL^a51YC^Ue0fSy&)*om8{N$v?*Vc!qpCnu!eTJR+;)i;8-FLw16eb|Ij zJA&ypSl72)ZW#R6r7Q9m0r9 zJ`Q`~p%i>S25zcoC9SAJ;R$z~5-fEiKi39tQ^4H483ATs?7>>y#CSkbioUe%!t~)M zMBJ3ruF9%$iJjgalNOiDck&rN+rdPac!iWf3I&2kfWla>s9$q_bJ_DKVZRt-1W$hL zEO@Q}#JPe>`Msu509FI!uQpmuYhmSn9de#IedQE&qHUrL`I4Rk7i8s*mZxo^7u+1L zbLhm$*dgOxwQOeitbdEm$KIbDlYQ>Q$>d4l*1Y^HX?J`y*)*=6Y~&Ku*X&f>dTFwLKr0>}oK!Fz-e1C9~GG?m%8me=;5u`>xUtb@aIORjTE z$clKP{=K#%WyY(CPr~X(HQViZqn|ZP=j#DunCat(+(bbP1&ZkDeJw#e=-nk)Sw&$l zB<_&PNFmJaiq|sdy@A48yPGK~{?T8gi^w&VRTPdW_M-6|I+2^oJ9Wjjhpp;xMb!4I z?RF}W25eK8y&M_Wnnw%BQ&jT`&;Iqn3E?+2^rQvWMgcH5gi93|6LyT_E5DfV|k#KQsGNm=XDiiYa`e zZqdZLgHDk2!%8fOv4$*?1tFH8!8EgmJrd-R?aZ1>}Y6O zqF6msRWzfyFj}u-yP`u^#F-rxw*@}{#$Xk`&>#57yb^8=61v4+QO0x%-=hn*4J6WKgXh%o=FM@~qrYtulW#Y^Dk?O;JCnC4KALS`F8ma%Bmxw|{^AY%{C! z#IeSUu*MqmM!EmU;(l``v_2`cP9wBlDYR}Sw9bZcu#T}EDYQ;1;sUkr4o2io=CL6? z$tuyTXb(s7glM(idg_9{+1~QF*3-)=U`y4H}Xve{pN#mF_g18c*^46-S#feIrU2v$iC)Ranq+FJ!eS#?uiBuTOCE^9MMYP}X3$ z!s{o^yRvtXWCXIc6jEWH)F@4d^??^AcB&|_nJZ`j$2R@D|D4H-f@_r6Gm6d8aA^FI z+u0k--As5#W#>f8M=;A9%hKUCB2Tx$fDT!n&vfe6A@r5)AG;{mWTLHptb*;n^o1GJ zCOml`f2W94(Ut2gassX2%|TFCp|J&<391UbVfU%e@qJe^ghE7BB;HmH_T7Cp09Mir z`J&3fQsl%I4RjST=Q%;%L57*m+C?#%1Gvez%oU0zx=rqq$nz3VjjZa)MW(-k%cK-Z z5c4slzBOEFO4BLE1+!8rW@vPXn+=)F{8eE0eBp%Rg3TO+-cqUoJ3gcnapd0X7r(Qg9VOJmqscfW3f^piWxMp`;g2&zrAC_SEaml zNVBwy-_h$=YN-Xf2RFp8V&AZiUSnooxpZQ_c08W4J(s;Fp3Wc^ApL#~C?e%AQHFx# z>b2V9lRF9tBd{VGXapj;gfUu@EH#DYwq)Pa%13PK!#~P@kw1ixd-%VSm41<&e4jw6 z7mEU&;&+&_ZL$wE`CPddE5g|+HH}}5DG&2wW%bVrb#>-Q3`wOs^K~fi8ICG2x5~7& z$M}%LRMe*(bcXgPm(&l2N$y0EgZc;YQr;i<={jE-Q`K$nZtNc_c;#DnzSK2StvS#;dMd!U8P@N!(XvN9y3?8rQdgU6Jm_r7I&OtS70dOxsy^ znM~UgPuVtG0PN9HIG8bVq@_~XQ6l$e=szWj4{~H;pb#_{7y>0RX_pD2cELp)HG7p| z@-c_dPgQBih6+2TE3uQIMJG`#79~~*S>&~~595xSTtqPLAsv^-uXeD! ze`A}NGFv52Cx%OE(yB?qx~WUWHf9J*u9<7u1*7PJ(xaO#AK1^;a!1D(zx~jzy;Bkp zOVX;l!7mmZRSNcTqsX8!E%z!;a5O#@PfGUGXeq0`%cHLMTvIW;$A9$|PykT0UP8bH zNj>o9|HG`wL?2b+$J&5pDp2o-+JJfdQ>otxh|PAs2bh#)gONVSEErVSqGz8(QoSn% z(-?X~BrVqojuG^7hjx*Nq%cNaJhEs(zPl%%``VTF#t(@E} zdep@i$fC?e2J&Fu0b-rOm4iuWiJ0-sd6Vmv_C-~5XTurNf&J)F>y9t(Cq#=RJ+V-^ z(jKLej;Bk*CWLdKw({0{+R+_h|1S5_l{i1kiv={#7mDmj|HK5nqssUs!b@&q0>$kM z{&K8WlbB01jX0r{($N@o<#mOQmvG6~UTqxLCY;cTn`r?V@xnp@Gw_TUBcuo->czrL zH<0MD1a{#ndu2a!6GqWAx+2uCWGNhm2OnWspj#9j zh-87$2vvS%vPC+UAol6gsh(_l*k_cH6R&*GC)~*6#|beSE=THbBN*($6yCuLvnfOw zokIsRf;)RhTp>-wN+IrA9N@`7py(8Jo^1ZEWZj;^6l$&|i&i$CoLgvLErCHgV>PC? z&<~jTh(_v*Q&Ekb=lfXFGW+=Sp6nj2o#ZpPmPMdr?XT@=4MQAeR5j5=bE|f2tInBv zg@TmDG_=c@jw*@5Fz?ADrQBFg>(R@HJf_Yk$)wShpI@#?nCI%SIGJLCj8@d=`I(+J zGu0^#4yCqa7c|(b=54aT5ORz5*EksO*5%;v9RvNp?POT~``!CzG!(S4veL6Q6m~T- zaB{S<|JMy{RMBui7DoP*d1OpCa_B|?93dGqq@(tOhtY*k`T>;=j3_2WczSMa!eq!; zKQ$E$Eblg>qn8NRwUe(hS|mX|1}0hlj>7v&H85WvKWU{n7LYi0ayehz{W(=QeU9hz zb&uGCpA~EeKCiG5zvqqal>%XadPT)PHm%!TH?mMBn`yT z;iN`tA^8>YCrck5U{Y*U?&>+B`(+}n#bcG*Hf!)@5FPEK@pKn!_^FS#lhd;EV-b5z zDwW>AR?7ZbhY+9^ChW7p$(W37Gk3FYu4W4nj2b(2x|B4?+xTBeD=Q)3SnFNc1g-l8sOG%b^fPi+=8g0#S{l$r86Li?ufvO`FpJ1X8$|j;In%6p zD9g}G)ps!`t%nny+C6qk2xPKY9hZ7av#y`2TZXVY-I|)4NHRDqv(`BLP0z7Z zOPCFrEV$+v*jdIvldZ+dw%=M@!S25%??!8oN461pwQUFBXq3 z2UjF2~bB4!DjLVBl1>9|k2-{Bd5GDA#|BeIs&GyQ)D@bl&1~KJC z)%q9qE{!$W3dW4pGerO@rYM;74*q7ZUR(G+bEKl<^Z|FE??APkibcjErpe+ywaF2~ zgtm39s!NSey3>{JfPv4HGy;66imk2-?_tVXhh#1!@-?~Ujt`#&ZQY-!9V5110j~&S zPb?)lN^=36^xhHnUdnEsgH)-hLiZqq4p^q{QHM>S@7UY2 zIVRQK2e)UN`uC`4w5EySUkGWYFl1&p5bhCF_rPo?4pMcOygW`A+lDG+ttoTw5&@=q zS2hl@FVDqi%7*N$-cdJ35qDCk`yEHWz!g(^h~Jh5JY1dwEy-Q9Ko!{!R1jM$gInMK z)+{D0v7=J_&cB@B&0^O7A5mQC+dAIH$?+c__DXsV|K1}{RMPmz9{Hz?#)`9+I%FP- z93|l+;imtSq*Q{KfI=h$Fw%Cgk%CH6rZFAp2MX^Sm?x<`Gw<^;4Z~}W$XCS8gaS~Q z*uBvd$9BeD=GDZ+Wk`0lHc-lt5fT`EQb1T;*giU&9Uu~@%&G3&0EJS6>Z0xB&&?h) z0ofKxo@n|t1^#tZoZSkEw^#)W1!qHDy5mi%z^(GCE0JYzgTxQSa>>An3ia4Pf$@if z3s-$)JXS+ql&5GuD8Lzg&3+jJ`36T_l~Tn1n2xEj}8LeeAWurkg* zMDkFmd31z->riQ6-%Ap#eB1M@;n{7zNXLpg7pl7Dh~^CJ)$(uwJQ$(>gX6$I=tp#< zS-B$h%IugG#4UkJeUFh+H{)xSV|F7wK|hZxO`XyE3+RYCV;BSqO}@q!f<)lXXZUpz zPqfP9N+9G*W?*cc@f3EjSg!Pv!{P=mCMF)sx`DVujr53pTB&bV18LGYp1%7l-SmRo zDdB2>AOB+nHCEjP=gnQ;n*2aKmoE2pdXzfoNE~=`9f7$L1cQn(0`LLe=vWpIzu=yx zHd&u98nF5Ud(tjL-(pGI*Zko~%!-slSo==QVOPWO7{~q|7bpT}ZE6VLLyUWJOcpFe z{?wk=cs$W`A&a0f$g#XDXkxRNe7mf>HxyJS&jo4-=}&V2HQ685R?2bBkK>a^yd(5` zgkP_LysuH-uMvb^z3g7XiCucXy@}JtHY!8qv3qUvs%Djw2Z=?{m+SGQi#UUGg_yEY zzrbMo-a-CaY-GDjV_Crb_;H8*U;HWlS!VpJuBcRl^3-zj`udDZ_mHucnP>JQu`&}h z+dV8Iu%c)PRzD_+EOlETwi5TNT~8OZUrYDc^J1nkP;BN9Mi5#t#$jwO9i&(z6fXlv z^;npENny{NmL51^70&R_Szoxi+HkOuiA(?Coiu&)nce;L^~CvdPsjTS@k8TJ5qQ!* z9u+eU=hVQF^n1J?osjMh1d`6eOSaQuvq88}s z<`Ki%cm2Jb?u)2G)N+V2ZJcL>ZB-DrfSL(`6T4nSW9J3d6ueD8$*G|xuB=^z`w z(G^g8S%8yo!JWl~h5Gc-_2Vj)DAj~|%kEbg4Pz-57X(lh;Y%3|!}h!>{Jj(`M&`h) zmZjUN=g$uQP*WugNGP5r{OqJCPgpZ2=^BcWBs@-R9ARmA$`k4Sf4av?P=BDz`7U8mrR9d9%1jcZbr zajxbvZO)Wf$^SJh4Kc>$u?F&xY3*J@!iy;jy-p+UV!D2lB;#T_BBLW&A?Rh*+3a|A zY2I#RBdfZ6dFkc9Hno2ff0Ja$&9X3;EX`F)07$!bR>ZjKO6kF#HL+Us{Zu-;yyP?9 z+q6{`)zuZ1bqNTdScR|EdB!m(y*IdD;zkr;X$2D9m#GHVyoktjFHCPFP8!FhV#cFo~rbQeTp%4uaf%w@_Zk4qJ1&Z zr>-m_uL6$*$4o-3c##Jwnvyz2vdo5bOaeU#WZe}MJd)WlUK4VZ>BG6ePWQu$saDW* zkGgQ?F)*AiC8y6hJB>#!@$}&#(>3W8jIN8hA6vce-`+~=KI&uJ{Njk0Bc^jxYXSag z1YGM`&JOt)+XTlVvqM;tox0?*dUnO}b?pvB!wa2JByM$eVg}vmya~DGC}LU&nUW>U zH)}F%2(q|Q#W;m0NU#TD5ue5Pl5LP)p)MhDQ=#z}b)p;hCxmAxvr%&yWg^@?GJ^<# zE}Rr(?cqg{thl7Z=QV;%RS@p)R#hH-O`;c$;b`pUVaEUHfC4#GOJA?byj^B&Wv^fEdH} zGWK%Wu8W%8oAKR5DF>p>$#W&TJ-S;6{N{BIbz4gyTegCuQlpsO`8SS3OYz!V z@se|mDxyd6BHL^N?*T#BJ*}k3AQvf*S5G(il7{4Zuk*wV2MRS)=#g0sNYY%i95>&fEoF)o& zoP0^w#Qh<<33TrZo=jhzbreFAu#2QC*G$S>dzl{Z@k8a+mcTm1(jX+9`YGoAbKXh zx2pMF0iq$*wT-Kk5#WB=s}*t)PD0bOIT%s$>TB2sK%1&w6o78lMhq6c4q-5CIh?bv zV~Fgm)L`M+0i%TNBDlW>b$KS%V#DDVVNuvK4LP#nhJ0ZT@rU2QG$K%;I5pL+UI^&_-{LzbtR?d@&<57 z??3b^ux{$QS>45P6s$;C*yLF!PeV6Y@!zIW1ZPw26dT2vj}G*Nnr@nS zE2H?>8JB$N&V!*)XGf4FIWHK3+0P(U<_!8Hd;@AV)HbfVtumHr%-Kina*MlA)9KrM z=RMtIw{V7i$3p>=PI5A?qu^JI7Vl#Pq zI$3d@@nnkC?*>A7!`YFAZnI;J^m4jgcWpq9M_vu{0263V^$JR9-tTqfO8cx8r8o7H ziopl2`^w-kr;lrs9m@f7sE2rBZ45$4|6Mv~j7S+3-w9_6yfO|K;2i)3B&@rXd`U&8 zLKy}3CPXijU{KgZUS6Zp6LlJ)D&W~>2#5ERfA8!>9j;zr>P29Q{1y?i^%T$?NI^M2 z!@$1*RPQ#os8A!k+4T^~YTNU49lX+ISM7i}DT_`a%oa3KhkssH5q#gOWxUw7S$4NE zP(CWMR-8JVX7CnDjUg(rcFb;Ja49mMx2fv3>rGEdVc)6Mt|HC2?l&;MU=X z@R=*p!FtE6hYm{KtVtQw1DXi;R+NWpGMj8F7)lgNq(S2xEgZQmSdfRLuS$y;RfLOc zm$S{!R+pJ0$SoJV)^~!>5mkHDnDMk#V|T9B4JJ6W2i?TyXtUdnfsl7FEgTQv^5^ns zogR`W9^aW4wHNlKxrnnOj0$`+xfsmfb%8=EJDJSusXgfxkExyUd?Xt(6HFk+2RH*d z17IFc$e5tVeaSNVUSuA{mn+D$SmfCQ!FHC=731VHr{N`LX*~yR@90(JKxh`Uedcp+ zfU`slTdYw;Lta`WB;Oj%(h>2}T8gH)Iq#~@Hs(w15Dij#cRk2W(6*A78f12h#7&LC z#<)5ccuZSW1eq6=yAp^G7>{qe5CnP3@A1%)MkxbJEK8NuJNK6=( z%w=-Da<(#h%1LK$TrK|a2c8i{H>+5+g^~nrv-<&E`PZ5OR;w&!jt$WQ(j(vNMqEv~KL1>FLw7~a9@ySf=Z?hN=g^D}uhY)k>X2``1IVwNPk&pv z*bH^Xh+jW&+A=6l_k>lo^JLT70}z?Zp3~Y2V0mT|nbXMuxf+z|!1J&}&3u-;-w$ZP zz|)uU3H^1C?e(e0Im5m++;!idurKc!T<3|3cH>snq&)TnBk|0_ z9bGMn(M5saDVek*{wuoJkjPu4G&>HxOVF&Fisu=3egA44=9!@I({Cf9*a&@E43jU8 z;a$jjShjSq{uY#*%w^B56^(A><6Y-fjDW?fJ-D)L;4()K8x$r(QcV_vFS^Lpo{}i@}iPMh{*rwN88!*eJxD=aEc7; z)>BA1LoljOkEvwh-n+VI2=zqBP7C0M;P~1baSZRKYwJJA0%f+j*o9(O@aP8y<%K-s zHg={V1Al zX*FE&veVc(gy6DMM`}Cu_sagUUDIN`;{dcwgKe-txz56CtyW{ZIv@?zd<~-0W|iyj z^w?$iGclSA^b)g$ET%N%0v{C~<E=(Rq;9zWBL!9?p~a1sMvT^nBPF}KYgRbg6f=h3R`X9{qt8?LUB-e#7GX+z z;`);+bU7%L8KDH_;$Yx)SwGtVDr>6n?NSVpQ^+S^%M7dnFG^@_+$yXK=ybDcS!MEj zhB2^;=?#dYG5i=7PY+sy;!gQEnEm7Y-dM4@;o0Zu6mPs%Z9mTB^FIVYZzgTd>M%U? z@adfr#?9C7Tu3HO2B5Utx|-8%N?o^x1T9xuLM4@qS9^*gMOW(V5a&U0*PkKRS?!;F z?R4&HiRkb6_7Tu8mb+CwNY8Gtas!Go@tFJl?&da)6+znp9RjL4P)^GV!wo{(PzLIH zY3S3f7vfN|9(ri56kHH}c1YA?uxX}z0AMICdE6+h z+v^&z#|{%&@Lf%gxg}-MC}>AcqgFzmg?D)i<_Vz$43AHUZqI>(9N1-(n2_5rKJ(Jd z>R3$hD9F;c0uBwCJZ8du1g0?;^_R|dk7Z7r4#6_qK`h=-qO7j^r@EGVZDYN0BF+u& z;ffQqC!!FPG~QuWc_O$-D)CZQ`VF%;xj#;R2$yk7OF5tmLm;K}Baan40_vgK!r<-m zo;cWvQ;d*Nw?nR^7kY#DZRgPCm%Pg5VTYA5{!+=X^B#PT?(A!Wtim=IFItXsb2_PT z&sMmY&orB}Ybb3#kr6gx&&qL6aAe`6_4EVAh8KSbmHKoH90~9fLKS}jh;2})sBjUxBjwWNCkUrgEmre8fkn^kyph&x(7EM`( zW6(Ke&`~In{rnrh5^GLEpZ0xhF(7{YApK7?;Q!M9{x{2_Y^A6uiq4ZBxMANZEC#ip z5{sr2hc85lVip5QjvA=|qhHRH`ODh5Z!splwbian=e4uDD6>@JOv>k{k5aM8(;Per zgI!U4>SVmh6UPxZUHi*hHs2Q@eaMv+v~F)73Kj$P{k{S9>46278}(|-?*oHp(B+24 zykzChiq)1VEYu~M4dl>HS4w*A>i~P1rGoZydpggrQUw;2Ke#WSRt5(kd)}3_^lGkz z*`u07_mr*vs&K8dI`?DB7_5LK^dD1?3LYDi>fWibqa@X*LF0gmC)V$&R(=C%I)uYF z{;J9@SRP!ID{)*X?g}8=iHTMiuCGsiObD0m$lR;AZd5UgZH|J|)#e1&Iff(nbT}k( zv5WRq=%Ct-89p7a)>9V*%g{qO<`X9F1kwwJE#-rFdVjRVgX22gI>!1Wn+bK>rCt5P zsvv6}hr@4t{~e<`38wl~g*EQ-j&FEODkm_dfZ z`0?U8xj|L5K~|^t(KSsrvz-^5U0t{laHUUSchWpYW@{?Xi=Lr&HU4~+6rFPFV0}{V zl!uU(JkQ$(*KE8z$%1VXOIcKd4Yrfqi5J{R#E+R~X$c@Gz{VcV30jARY%$oa3W#56 zxXujlXt~0V%nN{WPwh<#0)uV3iJ2B^zAyb#W1Y?=n=sB9fz!rXmJ8`GLZeh@i~~H? z5ENo$2kHOEc8SZRdoe~1NnNZYe z!~_>&!nvx*z@$b9+Hc)HAQT!`~%#xlg(1g&sqaGuEh{pZyizp)V4 zeT~55Sa$B@)sQ@q4cMhkts)i0Ce-4Q(b$%dgGk7sZDD1k_pqPxCZ^|;N!=4%zJmvg zp-|?As84u?T6o4U;{+!Ap?u$>jNGCO0?4dGvtEmp*q+Ouwqd|`JV+CAazfWc4vW&_ z!bUue(FQo^RW&5x!30>s%oqyN!AkXc6#Xd0Y$4(AOPa|rY-bWxypvCmQv{N8=UOb# z@&}nf!5sBi{mB!Ir+wT{-Hb~If7hFcunJsV|Hpz4*nb&WQ8BW2_o0cg}^W~pVSMIYIHtzDB zp{dTn>r?k+&juM?M~2emh9iR^UcgTHSekSo2s2uJ%9IJ7w+p` z*IEg&Imk{;Rw`j*Niy>Op$3mFjwY#SV?WY0I*mCE^(gnzj%8u-^79uZw#_=^P2GPe z?Y^!tmyHv1Ra^AZk~ztgg?rFcNn*h|w2&3jG+m-CmrmF#`9r{77W4P9f&Fyt%g6%E z#VeRvnpt_VR8&+bNYQ1^BfB!n@K4n<=#Q5!3ri2EJgWnfh_;6P7AOrDC_e&VDg zi^-dnGI7;=OFJ{6j1v{ADci+47kgIPXT#sCGIh%>#R|%mBacufk8mQ7AaJl$S&~qs zyugrJGJmT)3XjK_x3lIC{khvvX6n18+g+C#SGGbrVar^atXx*>r&X&q6SAv;wzZQA z4daRiPA6U<6qT2g2U)~_WSV#_$q#Tz5?^yw?mbr9BhKwom<)b7D(Ahwsf-}dWD?ks z$5n0^&Bz1&IaB!bJ10kE{&9KlV!*$T*t4kXR1 zNg4{aLA234EDR-D4MGs}7qP~y>Qn{nB zs_&5mavRbN#D8P&?StSM%pn;CSo)1C;GJcIOFDFBIRpU-HFVO2v^F|b8W+~>= z7dN`2|Jq&Ew5OiMrn4anby3tMkA_Pi^k&N>XF5O}kUQ5O2=3NKp-Bw(2R4wqa+(g6 z`}n&+9BPD9#RKn7uX+C-2pnusI;Pt~(S6vuquDg-0+*B`N<2`5m?CY83|^qONI__L zNj?T9$r)=z0sqN!A$ZWQoa}`3=W5?E!nnidknT57X{=vfp;Wlve}X?jrI-i#xu`S6 z6jAtaCX5md)C%_1NoM}a&8yUfe}Wv*0ePws@D_Gg{T)3pSmQdPfNmPQRxg|J@|T4# z8(|(rv#->!`sDyZ{&$66Mc3g=A=^O6>HqF}Wh$F0pztAj#s^r`mJY!Cb(#2q92 z&WF?junX}c&{l#eC&a8Rnb1r8psa}F6-(sl#y4E~iNj9`D)~M;$251#R+LiTM0z*N+6ju};=BbH-ySeNPqTJOSPxvKDkIGwo;T3hybU97l! zyWNo7fmq+dD*aVu%+B?mxi1%_O`+BSop2D;j|{)kV8l9>cU2N^K+DYElM7J67;MtPSV2V-OjBv2@zBX$m z$}A#JYa5KM)9S=71w3!^zw1cBX{j#SfUyGB1>3#^R8&KQnli*NHlgbO*oT4A zW|F*m=nt(sptfU}d^$%TGgnx_o9L9CwEM93>PxUl3tZ1MI_;HnAcuYqfx;_=P=4p) znUZsp$l;Mc+~EGYoV->QYkyOmimWOuDO^>Kod2G+E z16Dr=yDz5{-URAlajuFEpLMB$&ZFDa6Z&eC_trHR1B?MWVS-a+L(tR5S#iLj4N4l2I z=p@`z3B#f=gL}Vmy!DAO$#}M{23*4WOL>-N%1B8!pRvLSd!Yy#S7C%pF?W2GgAxo~ zE|G8^(N92cGdEIE7oKjtRKO?je;%)^o*{|4zQWb>tCIgMW$vHhYDe-v@%qpIimo>Q zl0{8a+OozGLi}Liew%z`rjbqu5b{XU>u{2i@&3?3S#O1BP60gy4DX)oT4YciwHeYo#LfSA(bZ3%#13es=R$Gz?*&d*<-c)UK)x}mKGa&r^9 zsZJD3HtJsvAsM>&S8`@ubI#E?C-M!j$E;K5Bsy$ENZN}UEdJS|g z_d$P;2pfmp4F?`^v=&kJFa@(CudkF4p*$7t9V=Iu(8V1Cw1!xl)N&4lS7(*Qq>a zIrFG9by#PyY`FyO#!YOLNWPN9XF0ts$}&W=`OqO9X{;OQCwyR?pe`*MvP~i68ltpt z;V7O@SczR3@g-j}UQPD>h%=U<#)bp`xI7#KWj7cn;p@VDU0!(x&(2aW@CRe&nIBW`-jkS+l!U@)v z%a@$+8dWX4;6)6V|2?E8I0QF_G)5`*{7l{h{)PU7ad6(u4p{wzTo9js4^E$)g*p}x z-Dd+OL&bv!FBa773yH>u^isG&+<$A&!`sE$jY&>-jil=-*~TNM8o#gKZ>-4o9xf3! z-IU?{QzA91?~bqs*mM$i!W-dFo44$nLx?&cApEPP3P`^~rg&R4>Qo91^PUw*JE`pw zy^0eD7KhSg53scfVe5c$i@#5UK(HxF#pdZ<*#ciV%8h@I@bZ?1YmrFpBIy0KE94P~ z-1%Lv!O26N-X^epsJxS~@|_JKhgso>NMFQdpBvV&5Fn*yYbYg{KPpaf=0ROlUQkPW z+*&KShrvkP-##-R;+~I1`WR^x@ywdWp)}o$U~5ee#x&1KkmG+M*Yyg3x`x>J9kctM|XtfyDoKT>N7K z_kTT2&y;bLzS!vj<0J(!vq{ji*hp(61VH8ujjFl4mL&X!&49edJ_E?PAgo0LzY3X| zr!Os3p#j6>Rs4@zArF|FwcuC@AuTpF4AvN?7*<(& zdL13vp;~ayp0=ZxUoITnbJH9RCoZ``4kLFNI(b&i*wO+5YxR2;Y`$3Px%s-y=MW8f z4Z17l(l-)YUB4p+NT~{lj#UU>pDvp72$@eTK%=B`OLN^TPXaGn>yBC06I+=sUAZ?j zxAcoDZ?(+lC>Lp}l-Jk#AWy`abl=KHl^HmUJnnprWzJ2bItYFbrthpej#Y%)cr4K$ zsV=BhefV(9navM`9<<7tzRv%FV~t_$d*~G^+cfA>4-NVlxVi03DA6@|IldrgAw^zm ziy8ZjzPN$v8s*>^tvnhBEU9GZY!ca&iw|UKEzqZ~L1bWoSE{td@wgRD)>>;k2vOOP z@+FX7s+~nGefauRxijXo8#A~jpEF~;y;{P-oFJtT~+Jdz>YNc{1Al@Q`$`F>T zJhj2j-fIlX%?Fd&Bf2cG#frEC51M83?*vRjvD1-|lh73q81N1gyaMjenb9_iO5Pgl z2%AEN6S?4nG$!nVGSg5D7E%mU{t%2&)t`XEwHgZWxaY);sUr1StsxK<^({_6ZAISP z=`MLjLuQU2Jmqq?*(Iw)+tOWTBR$x**e%b0AD{Hp3Z6*HxG3o>%6i@OrgE|f`0~#4 zvQg0A@MBfK#@;_U66ftZ`$oeBIxzK_FG71eEYgm6;~RniE%FGwVNXjH?E2JGrU~rYdH*&?%ikcC}b=s2?S@8Q0l6A z2YFM;={t2vK(j9

zUhjx$VXZ|YSJ&t~L|G*xxKYEyGto}S|j8Ip*Sz%3cgz;)KD zV`A#&kgB3itR`a=fwH&j9}JLF@wS{uyE0~&5E9p8 z8NBkyguji8XCS5|D*NduBM0=9AA|OmV+Qn=8x)(7=U4W5KwsQkB91=$=5J0Oy0LeI zv3P`vIA?ab0xJ&S^1J+A$^3KX7%AQI4;f6)y;x@KD)|~@3ELY4P&q`HaL7e-%yxcc7P)Dex@(Yk z3foAcmwK+?o;3>-qaX;JT_6tNfJm^1W&XfTo^@2r_u3fBp5}g!XNhi zt5S-+0eLu=4MTg8*6_x;$kr3BO-c}?vc&ZQ9dVTM;EBKK*9(8{g({>&llh+J@)kiU^7fE%qq4aT#%7Enq3|#p#8S_rteyVUMboP zHGV#uet-YQBfh@a=1U^L$TaeH=K++GM~nzn52nYjZ)>c8xSPmuepoVUKM6%-uWWxg zV^iNq8%wG1+JCEik;tx3{#1JHuTPpC=^S8UZY_Oxn^D{JRlBFAEM9fY_Ulou*`&yW_r(<(SR4{Q8#$){u%LkmT!bDB+9D z_qTd8>Jmy0rWXI2F)LfzqbQ+zv1~ZZ859=E67Wh?2ub`3p-?n6M^X<9l+QV6XdfnP zsb90I)2;hqDRcvdCRCGG972cxLdl^}91*pZabwNqbt4iowNa2OrGSY^7VmzQknQx**j+c+%ZfZK7_w&umCA8yg3_k0KO*>ZsHvSJM8d|3naM>V}OE- z7IzkWcMO6YTn)i4q8(elE`5mM@fAlNHEJp=VZ(1DrYsp_pQAOO^YodkrftOtq+ zOhrJQPj0GH3wqSu!k9j;cGBW|)cvx*f3K;M?79(|WK$-GBMv;3Ea!Q%tn8{jnV_{B zfHK2hcpf>+cDx!MC+bIb9sdFzqXIPL&mrO25V+9c0?TN~SX5~PohibldKY0+P1RmB zPog9z`9n;&rg<7Yy*2lCCG)9j1|vagVg}3;XFMi`Wy(GU$&WU)XEG5~MV|3sKi*lJ z;8%;a#Bb;>3E#fxn4^sDra?F-3<|W)%HuA%++r>!|J{5gm8iB8g-hzfNj z``F=7pzr${v99Q;sm%IAc(0^M0_JqFN)ia7_o92mb;B=8LIo`zGge!ICmn0(IH>h1 zMat>Wm8wr-GjCS=*Kc0-jXEe*^v-8(3poeTMU7_X!oPUF4l@cS@bEKo2h9ylh9HXMjyL)z8#DXT@r~5|hGlc+x-#m?M!Qc4?`jXE<&JQwQo1ldZU_~Qc87RTveOz4 z|ExJeX8-y}JOhTdo?t(zDA?F#coSz717RU{V--^Z@iK3`LGQ*HauQrooTRGOjCaYhP6uV-1&M1`msf zRC-p>A-?na3S-gNNy#GDc=?4ywm%#pvBr47j!MW^KnaVY}nv%Wh^*(0_8*4BH#r`eF7`YA0&%JLw z!TTiy_06a1i&Jd|DKl( zc#oG6pyRs~frOwq>(d724iyl!`%X+47vvP=p1__6csAT-$DTAajVW`oAm(jxLtAs@ z4*`gq&Eqm-i7}DhmF#H5zs98zPLi&tw}u;nwtHM@DcWeytS!^~CvxMO?zV?9)!`|V z)S@$&e}wL)vk7$z&8cqeBA`5CgV7A^>|&KG+2F11mony88MY*#LX4J?yD#5){P}>B# z@4#^%Ho)p4AXyXozP6CTlr&L4Zt}2SpVq>e?prb5qh00ZUeZLTvdOHi?7Pia-sXS$ zD`)dIt>+K+FOl>=SqDH_LQ>)yA+-(7``-UqQiThwKvch08h(BqjQ;j6Q{CEH(9p=9 zM9kn{x@Mk=7xHsox@Iu-V){9Skg6}Ja$)#_ir4vQ;|YvTWOE#-xMv8QxmptRMyY^> zp5$w_1bjBeJ&Jrhhi&{SoXe;w+8<9S(a+8prm{|AOa&h}#Jpy3SUzT|_@|1^Vffc9PH5OJ!SVlBE ztgvsxm3TILb%BbhU8*A%My;sWXqN9-kl~PU^B(K`R9ub}l8?F^TIJVxR}M zu}KIkh%}~|DgA33 zsfKusuRGti2jrUDM(jD$E``df{cdFsYwhjgD7i|oUSi7LEpARhDA2J6i@6~xvMy}i zH;?bYB4l-NX?46g&_h09A(mF z0W7q4BGsvWMH;~9c%`VsQ0U@%Wbwx(Q5K6+#x8dhjr`NBW^S>9Of$C_=C>+@yGS+U6a!lh#AC$_;g4L5hmE za2HoET?oEAorkb;I1S`Su?}e9xm#{nox#HwaGHE<4sHOc@2?E1R2qMa z^9u=IQt}?+IX|Ll;~_lwqVJ1;$o@2$I{E(m^1$iiXrV-Cv(|$bWf+APr4-eM`^0u> zUvBGyi@>(X&cepR{$1GyC>oc`>AJs1A!yc$)6m9wPn9%Mw5UWJk4vATOHM5M*dE-y zYm44;U_0d_>-@*8Oo#RNvFO=wgD;y9>KR+(@PIXr#T6Lr)^@vL9k^DqbPi>A@{S~N zcmt}uFq1S{;b6uYS8i%`HI=#jmLf5Ztn;zzvUZt4582Wx9JVfB`eRG? zQDca59P?1bks6J)jb2_}ld*4CbY}b2HeNd_hjoZP^No~6IOBt92iGti6>H%36r;LM z?*&{pkx0~P=i6QXx^KuJjqJYaI}>j_+2`isP?s(>MQ9wpYVWOoEybHJjRlWQz#fFA z4Nvh}9{+I&B_RfLZpK_bb^qTMqrrLT%%=SPDRAbp*)Xd8~ ztscWL6$M%1keU;SI>^1|Mtc0#$R@D5-wR zHOqZULt6Un&(RXdH_~+pUGNnW`Q`)?ma=_AID2tTxflf-rJnX6Ft~8oGvU2N6xBzT zE9}`T%=R%F4NU7o4?;ne=RqdvCWA7N9kAeKk2y5h*bb?nevq-R67DjLIDz4WrDwQO zH0CgIBiQj-OZrY`-QvKg7LV@i+;T>d_KCwti{&+`V+XQH>%sG6 z%UG1zV5_M=C?A+COQHphMOl$gvG`Q2@|<1USOc`t!d!J$cDg1aox>qxMlPUa4_zE~ zW$)~`xnlj2LRGFBI-XDjg#m$F5B}@Xpr;_myqB6n@me|P$U0^)5LR+hf9r+DJd~cy z*Ui*@$>tuC_NLy49qF3f1-X1wf+_USt#V590351nxh3Sgtg=bHS`O`5HGiu|I^m$< z4TNa7n1d8d*VW8S@azF8EaErhv|7X1l*S+N)|(wPWS*Kr`J74rvtGipN;rZoONv6ug7_Xv<7XMvA-;8MzTkoN>CDM5NAM? zfKMMpGPy&vVoI$OdyY)ALb<}sB^+phCNoWd)1zia{GH_vK0bg-7nNQJi`UkBc{N5U zxSZWUE`vN`8(0ZUPdCW?-sps^P?5}+>!A6&N|z6p6<6BJU&wZ+NC7PMRKvaSGOCUI!)?w0I!6->~~R=FO%#QAI&XzHs$e zji2UBSR7EU_VUU{&y?Y&#dKC{M6l*-nNg?abcf4F-<=ij#daSoNj8$YewdQ4eI{IM z>?FxQ6NpWZxRF=E{XB#1u=YU;=&`6v-IinB?%7-sgUg%!ySvfma1Kb6FU9i(S>N7Xn)+z0>g zO!Cut(p$X+WhtE4(=KNj z>@U=9e4&oxzoYISj(~p)x;3jW_k*A5WDK_J0JwTyUqtbo7Jwrq5M3m+Jd*r8Do_y7 zO(QdvanEvW2T*7gFqGf`2!8iJ+;~uPBK3TgA15a>*zJw}2|67d4X}M#=GY-66lP7P zny54IQ*bN#{RP><2U-D3MB%Ink##)iH&4t?bUhNHHzCYR@6=~W{nD{r>>Qb z=48M0?uBrdtex9hEB*xR`9Q4C%?y<|H>EnMd=wQ#p1&*fS_iJF?{on8$j>s{neU72 z@0)>?Y0nghsX&UNqdtVx*@tJpp=(@B=~O%#9lvVNuhyr=C4SQt&;CuIN}j8g*neVp*`jJd3B>f-2H8m*KMnKWQ?Nz14)TU8tt%9pY)A>V+u7$|1j%uLsp zcN=N5pZp0;Gjgq6Rh%`4^gHP&>l<6jG|2p0_0oE^8JqVYfhQYkGG0ND4l1~wJegqe zvoQn|nH+Rm)7LA+x1?N=^N2L@Ws2P9sIc%AogIn02F(K?m$5-KbVCyE!$vk*rB1jl z_|el>5P-ycX}()K`O*fZC^Vc3zZkWg50q4r)Jw5QC~qwGTWT$GEEdleD9R!YK%r5# zqM|CAHA_I#mZkKRhf$>{*(eP#kC##Ga`c^lE@b9TAL@h?|D@6D8=8|X)tn31D=>lv zOFh+!){jhYCvv`OxOk9kn|#fT3*)|#f$(C__)=e6!{R7iTGxf}m<#j+cFSg>f%0rT z(E1`olPY|iVQFuvZZ$WI$Cz8@FZ_r*pO{BM0$-AI?f;5wOw(j zhxQwp%x4gmAq_YNZmn&R(OFr4?#BS#R3PxN!qx|cI1_+43myeW{Jk6!fy9S^?9;>% zI2Vvy#(h6$1qs3?fb^qUjNi_h{ssExT%`IZ=o6xwKo|r9-W9f+F26sVD+l+Hj({V5H^#2(YquQ z`319LT|V0CA|{gx6@w;OLGMuGgpF+1DF1mJ#Ii)^$NDc0oY|6J7nTY6vd zpLut!xn{4`lA>_h{YN0#4l;Ct27j@b0;FnafJEUpWIO*KLG!{b_r6uzZ}GZ;%P~PukJuS!X9Z5Z*n`E2~(?2c}N~Di{_ADXkngfRjCF;Mrf@hX+4-%*Yu!_ zJ?EAffr!@fXy-$Qc@Y`A)cUqB-I-R@7QL1XC`p(K&jbj&t28Vlq z;k4LIgQ~QS&bFCY?d!VEU<3UdPEvQg8;}mg5^qtkM_)C#GOiO5wYlcUYu$kUtT<;w zv@)aZ`UmSl#!==^7qaibr0bXrVcpii)lZhXdfx0sNYAF#S&C#x#He64q+Np$1w!dy zwmwY!y^>gdR0iBWTobc?BdC`dX9DiLBM73a5U7lp*nJeWuxt^ch+l%ylp8 z!v1#3vC{Y9T?;C0_eFMn3ocs8Wydvx&e&3+YJ|(=_5OoUvG!(S+DuTUU*YVa@ZSCk zUC20(@dCOB@=A&&OM;a^vPmT-83!UK<{%cND5fX|Zx&|~2RpQ88@zkUx*LmkzlLTA z;qO$KOxZ*wN4Z{E3_xnjo8dhd&uD~S!6(?f+C2Lf{Y$eCEVfS|&6yWd<+ina--vKj9@?{17=qJnv3X8S$;pL*{u%J-A?=((Wx zJ%H4fr^%InIHYjmt7vxs*`V%4nheVE0Ar#qGSuKc&Drr&lc#^7L@7g#VPAw8JP_ZU zA7qyjnR1e$7y*_plSwOR5^88dIMR>L>OOXFoq59VMEGd*hymQ2!#cImdE?p+wkMQx z(33cg_1pq*(vp-(D%g=+7xohbRtx8-^?*&KkhI!%tg#(bfUzCT<*wHZCUl(f8i`z= zv0hTOkzJ{k)w-2w&|?!dkkgcy$Gkb|nu?DW+SnwX{r5z<(!FM19d0@wPLt&v-U!Y> zo>)u|P1r?=7-7O#ns$rkC?agM$S89%7ZrXc06M@#QeAEBTAUUwz7(k6-2ELS2GH?* zh)jAsG`&4q+OK7wM-Yomnfwctzd{VNOnR`j&aNda-AHE-_QHI@L-o1BV@5{p%Xjn*(b*p@ruOs54dA6s&VuwN%Rv{|PTTE4==Ek_>Q(|gQ6|Uz z+-QB+v^oK~u2SrPpJg8@a^rH6eL|YE^kHL^gP7nSjt-dWDniXH-b0cok#y9Rm9CMh zs1sqxZha_cJRw460-$sWkX~*e&s${lj95WZO69I(;$cbL?mPqNR%KI z75pBz>=c}i)=gsoz`a68=ybz6K!* zwT1+D4p^Oo6_B!_nL;V!AMF~2mPs6*l9}_sD>n1YEIri!zK_Bvc8@UEtI9raV}IzB z@rM6EF7S4jL`)7Qj)^)sq}vNk-79h7ks)&i>QUD zLN5jy-%RM7Z6O?-y?Ax6{F!5<4Ecbv0eftCf20S!8!;!jpc_n7++$x_agu)x6z??%`hL>yE*JCo^h@~MO|i^mhqW0i}ygL$8)MwZ)pxoHGJQS zF29uP13KETxF10$I%0Ntmzsm^ud|jZKhV(rFJ@W->bGx9|Gm_yOZ-nLO6gkZ8XNvg zA8y))|wnFRl9bl>^Q zpGo$vyFcHbGOjYN2P;&xLFgfV4x~h7)~=A^D6muju_%92s#aVEDy|l%N4KQpM&kXZ z%#aofsRI^gw+uxP_^8Jf5QXfZ=M43@9Z&j<`Z#(xN6Aeb%tMtvM0(x%?QzfE3zwqj z=q4bH4wN1#3IA8ZtwilhE&i{Yy&OdZv3znRF0^h%52b-6MGvikIbc$`jhUunX%Zt6 zFyV^LlBTnr1Qgaj^ZQ0O-`ygY@Iua#0K#fQZztU%^HXJs<`J>}ilkRBaH*g=n}$pi zs548Bo<4UA_hj9AtWcXljXW=;Pbd>%W3H2X;T$17Tc*`Dns98+`-Id?8AwTNY=!P7 zdaHpgdi(+S{BVNSE*{@tV+mW9?0QH%MeblehEOA!bAx~z~fFQ@6V{@q#gJ4y_j zNUIZ?*|Nsj+6ck?67qP)QkxxuG)woyGNr>RV?`l1K-_)>i>KL4N0ox! zrTtAqX{5R77AZldCN1+?q036XYklt-+GnR6$F;KW84|EkBG~UjL3{GM9K*Pg zIyOjsd}g`B>%DuC*qWTV0K89oaO(Bql6)5ey!I1@L4h+QfgEnE>lJtSG>FE=XaP5~ME)&pdFRfYngMnVYh?Xrvmiq&aPa zv;pi4w|+07&_1$%Vf#czRiq&|7h;42=PJ*CD;f;Y;&UK(MA1@m7C!ESaQ}*&Dr_mWgd4D6-rT z4}-2zLaC+tmY)Zfw_Adail{&rI}u?wIm~%_RbS=G_|_2O0Or*Y?TlxoM(_0F&dN9!nY^=h4g1NjY%4C;j*w$PcYxF?+cv?K^qr&R|XLSQd znP369d^ncE3dYf9GSQK^=RfPT{Iq;k5(_yj}VCcvPNQV;uyUGP!c z;U(xXFoQ`HN9T`^Gv7}#+fc_p%SSQq?nokTdLz~N^=%|0=}410Zq`j3X$zj5$ItpA zgJ?z&eO%_ZQoeDHZZ+gM54wf`Mlqki6tg30)9NUA;1|}ws*L%Nj5{>WJgOv82Tgnd zl%S{cIcb9spz!*6^MjD@nEGe$eAd(1Klqp2t)po;U}@^Ca6`wP3&U&B6u`sX+0I9L z-8RX~fAd)I_JWg?#|0{V{hGyZ6zom92krRnenya}X1ZP|8j;h4vsc%cR=@Dd7%$G( zCvU2{I!Ef!u_IoXW0_h{b9T}@u>*ohYAJG%*tjH}v`xtm=GPjaGY|l{RJLPJmPonH z+R?|MffHeB6iQ}@#ktA~6T`5jW7RI!V@$*@k<4Wb?+S0OniNst`-Pndoeinb1n#+! zLMAab(H2Qpq`#gubnQnqwX@}te5=(?UI-b|p!f5@t!5=ru14g%aGmP0fFX4Y z$&D>d`iq7adT>?rmg!ObB3`>jK^i#A15xP*dRN5lpK_t73R8G)Ve|iWt@y=m-$~vj zs|@AYle&T5ME>llLdCD7GJI~ZL-5L%?6FRKyWuos_{b^mfipz0y6ygyGabrK*mw@N zL$#(Nzfn2h`Z%1OB8;2m2CWWL+zS@tSB+$r#d5Yf7q^OFFdpg;SjY5~?op2_SuW-p z3_c~M$ctuBjfT4xLmoxKX(0P5Y^!7zNddx^Fc(~0_P?)@@?5lP(`C#?`{#K-S?$yp zc^b->z}+UwEw;b3yR2v+QoS{jUU&q5gw}=Q4l%-5s;gyWcNCVo0}JUL zV|pn|J*YIghE9CB;+62FumXw406WsR!a2hDc6Cj(dGdC{=hbES44(A1|Fx~%7n}@Q z<7Y=|$aIAD=kqV}@o&d1gkoQ`lqkz@-$?#zQ}oXp%>_-UFK4v{o~KUB!vipWf*HP; zZ+!gdy|e*B1fZ}IexUxKXu+w3SddJ*M##wPrJ`lcjd|$i8#J`VK50HRyR&A_ZR&06 ztE-D8ui)Bq-Z&UR_gL(NCN0&dK(lpZ1eKk2Z0-Zf@$PK)Mx8BXFYX z@MjJjTi=(z{8Ml^_G`0E&VRF?8=oI>6rW?J$UR-wPh%mS^KL=^`F7Bgvgo*qU=;a|; z@u?(&^&y#9z^UJ?FbK+aUaDhQR3wSJ_Ko#4PNVhwg>3Z3Oo&KYbvo))NbFV;aoTQ3!$>o_Ugbf1{5a9An2 ziGin!8x3%DNgZt$ZHTfT7+8~)Sm7)vUDM*?rz6FPj-woO*qr1%&YPXW6#pP zI(~Qa@*lLGf0lyv@s-1Y1|wRi*faqkE<8`b&IhHiqK7b2lv_bHzAm}ycP26W9_j$l zZwCE#5dHl~H&d2&{+KdnK2xn8g%4Wm(vRo%#uTCLUQk*Oui=K!V=gHE>dGrJa-2wu zT%a;HKV7scuWb98iwJl=grzmW-l|yLIJr=1bmSh62C0%@nq`d7<9X(ytAwVq~BvMsUSL=#UFN~X6oM38H ztb}FFCwLxNjwMnpv^SU9xrV+`%-}#qE@MYDzZTY*fX9-gOWj`&l0g*(Bo+r$3@_;N zM429s>QKloby_MA)GoG_FcSnkx@zEb#1i~z)E=!#B0yFF_*5@nA|^~4#STx3+<-P; z)aaNWh?77`+=N8)X>UCRmSj3N41iHcQ*l<&>B3ZZwh=Dl-VUx3s~(hd;~5tNP=(N; zjm9LckvYVz=Usbb=^K4|BH}tD=Q;ZHl%vKo=R*o!dQIRvQ`A6o`SU~aN-N>4U;@@+ zg_Os5*K`+kY>Yl>7^lrSJbhW@olva~w++o018;-SAr?W~KVD_cF-E8RbzH)7$~s~x zMwl9Y!IfQV1fq73;zm|P1#tR#ndQ}QkxfcbhC5Due-BBrR+ZGZ)97A&wdESKjaa-| zI1YB&=IqpE@LwUHZ|@!_IFoQ7OqA@EzD1_n`!h@$U;JA)l#XRV6=G!a=JikbsyJ92Ib2eP=hO6Xool%rF+^+<3c7 zIV%*;5wCk@Hjb*Q#DZ!nL#Me=ry5|hFG`tS*n~PGrKtu9E{sYgl!Y~dp#D)_2PEkN z(P21HY3fh)#3N|nd`pui3Vk(}hw)z3Si%D=%xIY9#Z|*_)`sXO`*QJb%Cx20p_Q_G z+4|K?IJ4kZXM(B=&)WA9htX!%=4WJ8)>QsDGLh))7^dwC8R(zNSDIzoG-9fe8I#EqKSo)MF=PD^!@7sK9{U$VZnT7YOy8 z`bf`+BrRnbQxR62F^rihS;lTC?FXG%bSD;EbpshPRDSRz(i8KdDx=Huvuap8wDDD~ z0%XVjzRy%q_^u?Du>9R|yLI1jF$3;Bxn`5Xw-~}n@dw_K34?1MEdE)qRX#MB5U#i$ zY=|E4kiu8nj)bK+suez_zItEeR_>G2bs*e=)ORvm16S`robh`qF`3L-|03$3{16Ml zy(Z#{drCg@p`4j9t3|PUTgHsv^?T+1QolvNPl!8VmH!lqA%R3Z@y8t)ig0yi)c6w= zH?}Jtn+Q#Fmd`JJq6!YH0G@NhDeGEX^J#e?cDZMeKwg*`FPlZZKa*#BVC(1Hx4TMh zQU7ZZXI5=%dwuE$BREfUOx#h*9?jJGq=?7{4dTKyhAI~@$+KfPDQYdSn96TkQKIZ9FbKP^$ov-alrdb1UT~3A<%7gdTX%+ z=y9l}V}&l*N&Pv5diVR61c`yh1JSd2zby>)GL?I-3FBPR=AvTNFXskTU`3MCmecrx94P!O6K1gd zc9HI=7s=54Y%rlmFk(!jeQE1dC!}C$|QvS?`F9f&&%LNW)7~ zYi4hqS~}2sBh`pR8l?dtK5yZuw`iFa)Pz|vSaAogFVPnpl=PUw(@re7#?=s?cHIcV zL(fL6Z6&3n!G>-E`PQhHvn!I8CkIZvw;KRlGn!z%JTUB{R0EQxY|l2)@%T~uQiHEJ z8$P=#NT0F%iey7l6%%<~xKMmI8x?=~@KXJ`i=|MMqQhZfJTx|5z<6XR8`uwidpL&@vH^@Nz{cy zDEXWu6dTeM{(l&Iryx(GX5F)FS9RI8x@_CFZQJUyZ5#h$mu=g&ZBBn@&pr`*B4*}9 z}tep^kUzpsr@&@sPvJ(d^p!jb?+2e;1)IFhDmsXF* z2yn8-6vJ$ANoepmN-zcpeusxH`BAZuw@_v)h|{PNT$ z0j}$*lDJHKr;~&G#_eOwhCx+M#Z(nl=7Lv~vtrsbg#Go9wSKISK1*wb2+p;Eq+`R3 z^rLrZAD7-~tiCvu=C}(VQHo`eiV5M5&$sx>6x`EGZt*D^+%uTB^g03Y1M^eaSK45C z>e9GX&hkq0x%53zcBxRPKjx)8Z@_+EhxK^@#r^}~zqGq^2+8tI7wn%UYrx2z_4KyC zuJGqC>*n=vXQIda_6C)npYav(Ee?(?xX136pvdly{Z$?_J7o)@ zqR)TaUof0_p?ZF|=g|H+PHCJkrO$szf9cgR_$Tv6?4XDBRlC@v=b%UYVFplW2Z*^u z3H}M>qQ)7_-y|I^nR_(B1d;tu~!F^m;`?HoF+-qt#R^8{v z9erUf|1aRoh6c7>#zZwkIB1EDck2$Kj}~Qh55_CpfKVB6m>h~*8ELqTlxxzh>Qh zi@V?t5=m7e8#BsSEm?vqH0j7n@pl}TZzd(p+*p;^NgFE?+F_4uO0m=juw?iHQz~Vg zM}Q)DtX8MH&D|imLe>sFCfRdyWlJ7y_5{u}y`ED#i5tghVttD{1>EadXO{#Z*lCZN zY=b4Zc*!K|DhDu~v&6wI<_PImSx{)djk&(k8S|5sr@qMTadK2eF!GkDL}?&}>%|PG zw`We0kM}2VzmjL9XCkpi1s%&9dnEzI^)s14*A5#7lAHz3FcC=uZ~G$Dc-~E^7^5}W zsr|V*gxY0OQwRCSRAa)LbtIfQms0?&1Py{ydqEOdwD)SC@ZTLxyQqSAc!}{ors?S6 zPDXpqpgDI(g+Yl&u@|E9KR?(`pT$moECG(=2uT-1fm4QDXm#L4QCh)x$AP=#A*7hP zD65OxI;e%s6fERJ^88GsQ>`zg+ts2etA_ZaB%#k_mJ)hiyev!00_7RqrWzy?TrJjM zMeu0NvzwclmU@q&e{Es^P*?OkC>kmT!Y9xh(Uii;nWJRwXI(#S_e#7MdA0b`e$3EHqVOZ(;#jB+gFnZWA9x9UOc4*1IjW8O{C}Zh_+N$nxoD z>o`e!(<)fv?T#P&swD`LtM9p_rawP3Ax@$-l$|^t@1#mhu`tQdk2Tett4wTW&vdsY zoy?Rz>pa}Xj@9B=My|{C8am|{<&Vm8Tcu%5l~dk${U4p*cU3T zesO+rp0=0{u|+sN5i`;BJJBa`fODr&Ivq!0iJLq4`!F0Hbn(6K&m8EW$uw5_+Xz)k za%*)An8b?jUJJc)%Q+k~oQn;m_VY=gyhgnUe(8v4T#EPP+&tG}dQqYieA zV=bs9CuUkJPP8#DcF>SRvFlEbo20XWq5C^dR*C+m9J4u#c1m|`8R*(MQAIe&%Lf2| z^#Fowx06KB+*%7-z!VQ{FfySV>V*)j6dIC0qIG3u+eoRQju{_k4u#~iL?QK*NI`F` zR=v;OfR^1ldew8w!n&+ygmcL8>#|mvoSaUom+vXIqQPIxij0ZkaMSVKR^-#pQN<(; zk^)A?=s+{b;D`+3^JF$j`0TWHW%3r82?JkpTuj)JJvyNHzJI%Me|mQrUZVys7=v11 z?>tGb@b-E0*ii2L*izpxK5^=P`t%oWr*E&ReX)zn1oI?yS;HcTwAc z8HYJi3*T1)LwrGH2H!8CC@<8z7h%u6E>;w0F&_i0RXx`xATP%CYB?Ytdq^;04*=sF z>4evbr}X+PsDpLdyE3bh`dXqZp&AD=@fm|AKK-#WMCnEmrhdwSzhgwq=|7LBNprbH zd0z5|U=LQ`ZYi_nCN39sE~>JkjGnit9~#&*c*YNh%krhW4SaXeKkV%`>k>p;hu01>fS z_yg1d9@sx%msn`-zYpu}(r04(m6lw0~u7*KK@E)Szgf@taibXD9y)j^38WT-} zG#Sv^L?v1OcT7wh6oLI|vti+Gj6*V}O+5#Q;`QM25T)Kz>VvEM zjrl3uQ|!%5vb2N6&_{{=l`M*~aM@r%p#;}LOx98vpApa`kdl`4>+UU1&I84GD4Cs{ z3BB^+)EQIn@9Mv-RZ@yQtav4Jyh?iKCyBqValegF-xNNK0dCsx}c7 z2s>C|swRe=xP2e9dN14ZAZ?qWVYI5pSp|5^_+&_FAHY3JwoCV-@KDHz>t})ZAjBYe zC?g2P!bKq4MyIg+kWtx~w@WvrZdTaE5(=zuYkV0PO^L7)F0bb_6)%tqDn>spZHnh- z7(+lPXnmT6x=<9H6r2_U1&a;3!Kr3ybL)6Z;%bQOE^Y*EUIhGfE_W4E{X0{GDWwHSN5FrF_Jg*mf^%~^B z;1?8&M=}ZEIi2-bz3*20M6l^OHUO`3ZQpw2&`02_os>(#ZP=c~X3-5fuze>=^m-otiq zGGKP~%H}FI#pBaaF!gZhE3k7ePdd>*o3m}-#HT$R6Z|6FS0T0Qa)2ZMR%HYu7zree z5&xth{6G0 zYg#S-{a`_=1*SfckotB7$-l05C-haH@YbE=yBWm)-=K!_b|%ny*CwzhA;kYrXitPE zo{`>+ns$0z?>FOU$Uo~FjzBimL;KS(*q6YLLaFUIy~d#Yw1e3qK`M~A)zIlZ5h{b8yt7FeuY2gC6h>6~HPL&|y*-MAJBi$xYDY5`2_N zpNzsVy*ie^X%jOIn-@VaBm|)(YMxoL;*5!9pgN}5*0XGz*c%@PLnitd6p{_lX#ZNt z1c-u&f}7h>-Zu2;>iAiHJOR48k}7cTWKXHJ9X4rPX12*GrC_jbVKJdi`Uo?`nP z*3S$=PUh(c2&Cxfo%b<1J}5?`8*F{cZx13BoBDbpf^`^5_b-c*((5A9pFvJCVKkx!B+(P1p zyV{y{L_CbdP(Alkc=5ll6!!%eOT387n1YQ98Sfb=!h4{E{!ZP85^TpA@kfM9xDFLM z<2$_bTik)%wK+{(hrsMJ)S{3q`m9 zAP@g0cp$NFW|ViR-RrDs1lpi2e1o)wBM_;vMTgrVW62=FU@yS15F8sGP!&otO-G!f z9hI=gAO_Lapb0y7oH6j*fQSsX3sJaX2LMWN7V2davY&)byx@*H=7~klj*6jXY{fmE zXpPtcG_&YOpCUA|@M6U4d19^=p&oGmU~#Rjv`$8N6pfgEU{5){;$u`1jvOS|^4mqS z!GnXGai5Op3;XH)@j0W|P4I&-F3}gNcqEre@B?=)J{X;;bEN-*pDwUp<3ZEv4h3<_ zi)ovArsJFVJGwaJ~NFo`Z z`HNBb=MDz$Dm3RpdSVV*UyCfOTO;Bicv}6}t4}P@ zN^0bE5}{12)ogXLLZqgAVn=>B^&;ras7S(nlnvd5rnV}aS}fp>m+c5UNB-8qDzJ}< zxxm0jdzXVBLH2*vy>nSd<8V9;aH0(`Nha{rrY=GaIMWHP8e}H|MK%JB*|3J5P&)L8 zp)^N^4#7GG!f}MF0sJPQ-|^wYV;TGgoX+gjevli10T_LDz>OeDyUf6ik-)cXA#JF@ z_90#@QS@M)Sm;oV4itZggTivu<~I#YiNVuZjAw1gBl84V6q{>hmA%@CsfefmqV)g5 zAkBz)w(aE=owEk^QYh*1e1&*$fymQ`jCuKw>Hr8Ua34a ztJ;ZU(p>e#)T*gugHB#ki{ z{1zk)=V}pwyZ&!J>5}uylpq1*ITce-?k^*>a9a~ zeboT8#yJ;Y6EF>%W}L$?* `9EhMB;5O{{TVSfA_hkt;HH>3Y6iP9t)X!KGMhwGl zR6w+s6Ta-5sdf$SWHo%~>+=}@iE50@`Ij^InSkMr5$5d+ycqTj_X!VsDJH$F7eVYB zKhhUN(ibMu7ofxsjzn*yG2ZAyzR*Lym_xqc!$ODxX10QHm+={dagREF=z}d=oSU-H zJG~*h(PQg;w91D$D0TcsIE4aiYYggH)nnc@ zXp;(1)G6F|tDZn8-xo+p9j(Yhsi>n+(P~&)IG{rYnMw&^qZCF;)$YuJ0XbnFXbRz` z4kK>};TDOvS^;GcTylypLnSN95ZDGHzz&k0UcxuQXHLdJQVnJvg|+`QRr4?z@kRgn zHRR#s>&~zl+;dYizyH=Oj(Sc;%#>izkx0Te_}uMu|MTkLFElmCoX?EV%8ajK3UWw| zTA)A`I5$qpAx#wuQvzp3bp%kU2;eehwoxbz($7e3D(o5(vyF;bN8cl%BDbzLYS3vN zl>)#T)}$EZflDK$X(KmlGJ-m;MU~SHp~a(3904d@vIMRkQoUcn6iI*SW4tsXLGoIWY87b7^OLs1&+iRuTAs;D&=GM z>MPF&b@!84$H1Ro2AhVwArI1qZOwie(idqeH_{^^1RD&Ws{>uPP->nojDDB(BKVz{ zBcL$H_mPNmu!LSy;d-!z7$T1?gkIW)-SaRL$w<{o{j>Q^eWBxS7OKQZ%7qTi^9dp6 z&1{t`>*abhYLl%Vkspm)E&LZvUz)4;=1B%5MxGdjUIn-2`XVaYw*ILacpYlhv5t_U zo!SQeajfbx^*lK}1oCM?T}S8mD7AJy4z6sYnyp+^V@tZBt$H!7dcm!J*p6Rt`(AYW zUU>WMknk^F>1CThtm84e%64*2u6{V;JuyW)!*li>Ts>FkpDk4n>$P?@SLbRPwRVXP zuI>eH%TVesL&;BiJ)z|h`(5GtzL$Q|!Buqd+bO9h_>2#mBRTh*JkB7VApZPVE2qI! z5!VHJ9MSrDnBb5(Rmn{a9V2AmlRu zAN*%aGDvl)#pcXNM%=Q7rz2AR2~l(W*Mha@5AG2nqlzbv7}bqX{k?hi^>#4U_{V7i zUoX&}utw1*cv)v$zVw@R;?rf*+^S_!{X%TGd*Kuhxx$9!$1|Rr2yP3>z8E*Lo~h=H z4>B@;05w2RVFGGjp>i2kC{)Vk+o4PgV&$ z=81}x8p_qjKShQ|>dohjv+iPd~^H~t}=s+_m;Fg5*z8?HLu3$4re zTXz#&f5))HC~xkQG|l8Y!Luz;mU23m959_r7Q#s7kB0VCUrd5fMJtFlIi_#G5Si1E zwmPw#xMG?r(Kz)0y2EGe&h9hfbse_y97v4E=wC@qS+NInB685gj zA?yoIURl>q`(!PdIN3s#In^CBM=Z_tMbuc=piY}^rp<_0fAlKI{>g}U$)4EzkE%-B zsQzJpBz{)Yc{Cd-_oMy`Ht(ZvQ&T_v=N|H{IOjr{M9L5hYmOSE~?8hbtKFNz@h0ubSSLiOAz-z?t|>>o8QDXk5YTD$gfYzy$}Au zy!e4T_zm26#DtxgS1u$$4$#wx{78+1h6zL4WVQM?&#A0$$v2H}E@q zXg;qTUs1PzqF-Jt^r3EUNE9btYc6$`;1$CGHJDQ^dpB2=S-N2_6L)@e+jRpUR~|i} zH;4yc$esM5FJ<+5cvX8tyT@eJ5Z{fn1O5=kuQPo^8-~##%nx#%Mec6||5J)aya*<@ z`cI!301gC1{r|g)CTnPE_uo8(7!~RN;NQO?5-(9w)xi=#ScL`ydda(40}^kcknn>< ze>q{ZDFXnPrEctKuYWo{=7a&o<5jBPloPqKwD83t1Cx&~^6vinFO#32PgmRlENn&L zhMnN3v%iW9Erweon{)`q&gvs6sBUf5Rn6 zF0g%Bh8-V{q6<@=pn(gE_caY!y;-QY-n`a|W@_{?wKfmwDqmZV$M@KK!5Pe=+iJIR zMtSEFPC4{BzPRY^4|mVBE%wvVMBwk?lKd^21zPFw55V^`b+YM|^xF4wTFeZrsYB z%QoX4k`E}R=g^Mfi<*Y(r=$_JpC#)GPi6_Oom0w>lB#TRZUAuO?(3lZcc`dGElZcnjRPkJ|{okgfKqO?h^mq1SkfDRveVz^mP^fw;e~@3yvGz0dQWsA7=Qww2PK8J{4EyrTI3;N z9sVtrpC=aWqAMtp0`my5CNW|EKl7uZu1!7lUw#<_WL>bq z%Fqi!QtyY@K^^_8Z((I-Ly3kn$nLMIxeY^W%PhG`zG-A}Bgnsj{FCo&*-?i}Q8peQ zJ6}$^vL6mkR&Ib&6=imr&5{(D(gMP&gLF_7Cx@5_K5rlKt@ z?7T9_!ek`d|8TjtDq_5gQ^kckgj5kowBcMow_2V^`VTb8{y-08yCDbLgc51Rv-UCS zFx%c$u9d^Q(A8-TVo^y3YyY~=1l}?f0~LTXP>5=w+YB1wl}9pv2DxLV0dt&nEFt2# ziMh>8oyoTxnn(7ZagEyeTsvdjaWHjBDNLE%Yt5zI6`*aQz^aFm*_9Mz!_um|;qIfnzWDJJ1*d)7 zsmtdrur(hvoI3{a*?%h(1X(NI!?gc9m;7#B36OV1??j&DbjLMZ!9;J`#u|MFaODsU zP}RJ$J8q`gq7S4mS4m(O`2sOf$_Z$y0Pg$(sQoM5)H^WMeu|>gOD9B2KsljOrNEt~ z+^2zjsdR|0p{|z05je&+KQRECR1kcjTt!X85|9a=(Q|yaLRy$3s$k-u*z;j`W}k4(WE zk8#Ao-04t9$m4)7IHO-LM$pwsk3{?bPj>h$idpx^KZzt70uT`A|JmPP)>GKt*4EI@ zMBd>)2$KI>mYky&ejF-qE(vbU0reBE$A+v46fO(R*HdW>zgv9Y{djfnCw;s`80_L_+00_K+tr=mpfOmi*dZo5?PAJCct)Cr z*(bNwMBvDjNA28(BmBki`q>tq%5~oTmokNoHDt0C0r^S7gC)#@pf>`omV3wVY^%Cb-|a2TlV$;dOGngQ2FBTt-YJ zwo+hq2J0nEcLI%*x3AO(I9*)g#$Ej>w{D5cYt5n);EKI` zK>XZo&G7;M$2T4W?9gTfN2k&2%L;sF`4-P7?bFk&4iaB@d%TvBf1kc|)fQ{c*1mjn}$0TFnlvybnQJfxgj_I}B za-Xyx#7U->@tM!3z*w^<`6<%hf4ta|Jn5r`)@D+5usUrp-;m^0;-0j!K}I8eMj9-c zx_FD0yL<~ruk4^P(8$rRCS%DFX}xA#Hh=qXM4B3gr6@dO85j30=}0=l!b8zY1&f`F z@!E(o{lh;j1p2k{a{Zf`w6@Ez_g)GeJm=z~gOPEI(Y?(68XedpE6Z&9BV)!od&rqw za!SEXGSY&!gPGC`hg^VFY*erLTKO|6Qf&)yvs|N8X*sJ?-o$y)@SpeL`4f zAm;!}kGYQPbY1cw{0ei)H7kEk-(lPT)>a^~CqeB_T_u@6hhw^zdr%e3`fX-ROYGRb4()_%lpc#>b8%_$~EAa&7iD5Y6|+{y_`wFwKf4H_sM3py7(5F#(! zE7NSE#B&R|^EIygSnS!9R}Uc4srx4FwwLE#XNl-)%AzDgB~#jX51YD&5^0oXgmGS{ zLT?Dzn}LkJJIKo2*)m3nrwrF~jUq4PCEo7#0rk8T4HeZK?o}tulSw0{jZ5K(L%8Xy z1S&I!Z8$#MoYI3tELz?W>346FIqyadv56z`ucSx$#^`M*H&y3r^o^xcAtu**JWsFl zLn+TpGKzYFB#tam*vQ%eq45Kr2h`$IaF;#vCJ*E_QiX9OzMCN@}Oyg`)l8EDE z@S|pYPaJLs)<)}s6FI#

r6T9k`3Qu3|?nk%Zsi+St;89-&5yV4X1>E zoDJQnyX@P`9Nut1-WX7A4*`AmaX!e)rTf?CzW}g)o?-QHiWO<>pzXaomcfFS@r&t{ubJP4f}1tTO>03`wv{?9;(f^B-g z9gN#)6Xq3_4-{H!@W0qfD~dFOCC8>_Ztl}-2M236{M~+_vxByvnv*A#w3wp>HU`Mj znzR!(SmpVZMWx1~BdTFCO$RaK@L^8eP`X)&ZH*+CFM_vP;RR4vSA-sgl?X4N+>8Dz z4_&AD0}Ux)3Ts9Ze}lRWM*s5a^K5x9pze|1z+059BH;3^?iWz3985K#4aQ$|UfhS5 z3qw5A-$VgecZ(A8i@buOCd3`)s}To!kx7E+zeHU&H}rQf{EAuRp){FkNl#Z4&4Gj| z5=bNm*;62{Y=y(f$K)W47}Y=Zo^-osWV45{P|FO)$Cu|AEe&cDwzfX9 zYQ!%XmX~F8Mk-@*$nEyg2gZLqrWE4;u7X~7JU4FFI*j(O`Ey8wJ0;2+OK8p~z9nT@ z&gvlZBJ%P_-e6MQvc=3P*tuCCwMswBXkHoD;~?j>jX$@c{%{Dh!5D!J6sx}FK$3a+ za%MtGz*H?75@3f9n{^)A~eYczgYw^+R>kWAm*;*b_h8)0I5-lQ5IU_M`6A-SErv z%xOrkB)KJJLdnk3yOTvi^YymJ)>K+Ptfo`RY<--M*Q9&B-sw9Hipvu_fP(vF&$*DEXtO_~EqqrtE zDVIk(1_V~6y9TDys)-scoQYYiU3=bxzuOi4>TtPl+NZ=A85m z&ATFuzj4uIGTsTM9H?qhrH^q+jbg*9y*EX|JFG^^mUTh8N+qMlY|W~rm$ofnQ7mx#u*7=N>_xraf1V4LpoqXKsyC0$_r!qcyXrB= zy6S<}wY!EMuoSA-u4>z|q^7M)YK*aQj-vUcq^+A;K(DTDQckm^8gytSt!rFmZFchRgJ%XN#3yh+`Pq0&CB1eN?+nFyTtFTM1Jw1N$ct*3eE#^%XeyUVVMeNbU1+0%aQ9I?eq zyYgoBTvxlxyy}JSDKO2u0ug*qyyd+pnPB5wKJ~pZ%~=G4c}d>}yugq6{fC_aQ6UeC z%0D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 z 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≥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%0D>1+d;giZJ7s_0K3j_X9S~I1 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 HIGX Date: Sun, 1 May 2016 15:13:44 -0700 Subject: [PATCH 202/233] Cut 2.3.0-RC1. Authors, News and Thanks update. Thanks to everyone who made this release possible. This marks a feature freeze, only bug fixes and minor tweaks till the final cut. Signed-off-by: Chris Larsen --- AUTHORS | 1 + NEWS | 19 ++++++++++++++----- THANKS | 10 ++++++++++ configure.ac | 4 ++-- 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/AUTHORS b/AUTHORS index f52737fcdf..828a00bfd9 100644 --- a/AUTHORS +++ b/AUTHORS @@ -24,5 +24,6 @@ Chris Larsen David Bainbridge Geoffrey Anderson Ion Savin +Jonathan Creasy Nicholas Whitehead Will Moss diff --git a/NEWS b/NEWS index c0950558e1..d2dabe3de2 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,6 @@ OpenTSDB - User visible changes. -* Version 2.3.0 RC1 (2016-03-28) +* Version 2.3.0 RC1 (2016-05-02) Noteworthy Changes: - Introduced option --max-connection/tsd.core.connections.limit to set the maximum number @@ -13,15 +13,24 @@ Noteworthy Changes: - Added MetaDataCache plugin api - Added timeshift() function (#175) - Now align downsampling to Gregorian Calendar (#548, #657) - - Added support for latest Java versions - - Added NONE aggregator + - Added None aggregator to fetch raw data along with first and last aggregators to + fetch only the first or last data points when downsampling. - Added script to build OpenTSDB/HBase on OSX (#674) - - Added First/Last Downsampler + - Add cross-series expressions with mathematical operators using Jexl - Added query epxressions (alias(), scale(), absolute(), movingAverage(), highestCurrent(), highestMax(), timeShift(), divide(), sum(), difference(), multiply()) (#625) + - Add a Unique ID assignment filter API for enforcing UID assignment naming conventions. + - Add a whitelist regular expression based UID assignment filter + - Add a time series storage filter plugin API that allows processing time series data + and determining if it should be stored or not. + - Allow using OpenTSDB with Google's Bigtable cloud platform or with Apache Cassandra + Bug Fixes: - Some improperly formatted timestamps were allowed (#724) - - removed stdout logging from packaged logback.xml files (#715) + - Removed stdout logging from packaged logback.xml files (#715) + - Restore the ability to create TSMeta objects via URI + - Restore raw data points (along with post-filtered data points) in query stats + - Built in UI will now properly display global annotations when the query string is passed - * Version 2.2.0 (2016-02-14) diff --git a/THANKS b/THANKS index 05e9ae58ff..08c55697a5 100644 --- a/THANKS +++ b/THANKS @@ -13,11 +13,16 @@ Adrien Mogenet Alex Ioffe Andre Pech Andrey Stepachev +Andy Flury +Anna Claiborne Aravind Gottipati Arvind Jayaprakash Berk D. Demir Bikrant Neupane Bryan Zubrod +Camden Narzt +Can Zhang +Carlos Devoto Chris McClymont Cristian Sechel Christophe Furmaniak @@ -28,7 +33,9 @@ Gabriel Nicolas Avellaneda Guenther Schmuelling Hari Krishna Dara Hong Dai Thanh +Hugo M Fernandes Hugo Trippaers +Isaiah Choe Ivan Babrou Jacek Masiulaniec Jari Takkala @@ -42,6 +49,7 @@ Johan Zeeck Johannes Meixner Jonathan Works Josh Thomas +Kevin Bowling Kieren Hynd Kimoon Kim Kris Beevers @@ -65,6 +73,7 @@ Nikhil Benesch Nitin Aggarwal Paula Keezer Peter Gotz +Ping Yong Pradeep Chhetri Rajesh G Ryan Berdeen @@ -81,5 +90,6 @@ Tristan Colgate-McFarlane Tony Landells Utkarsh Bhatnagar Vasiliy Kiryanov +Vitaliy Fuks Yulai Fu Zachary Kurey \ No newline at end of file diff --git a/configure.ac b/configure.ac index 8212e4ffe6..ea48f48ce8 100644 --- a/configure.ac +++ b/configure.ac @@ -1,4 +1,4 @@ -# Copyright (C) 2011-2012 The OpenTSDB Authors. +# Copyright (C) 2011-2016 The OpenTSDB Authors. # # This library is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published @@ -14,7 +14,7 @@ # along with this library. If not, see . # Semantic Versioning (see http://semver.org/). -AC_INIT([opentsdb], [2.3.0-SNAPSHOT], [opentsdb@googlegroups.com]) +AC_INIT([opentsdb], [2.3.0-RC1], [opentsdb@googlegroups.com]) AC_CONFIG_AUX_DIR([build-aux]) AM_INIT_AUTOMAKE([foreign]) From 92762f69f27939365d3b06c56af4983b1f5f89df Mon Sep 17 00:00:00 2001 From: newpcraft Date: Tue, 24 May 2016 04:47:58 +0200 Subject: [PATCH 203/233] DumpSeries doesn't know AppendDataPoints When it migrates from a source tsdb which has `tsd.storage.enable_appends = true` in configuration, DumpSeries ignores `AppendDataPoints`. --- src/tools/DumpSeries.java | 16 +++++++++++++--- test/tools/TestDumpSeries.java | 21 ++++++++++++++++++++- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/tools/DumpSeries.java b/src/tools/DumpSeries.java index a7e9222fcc..4bb1887857 100644 --- a/src/tools/DumpSeries.java +++ b/src/tools/DumpSeries.java @@ -15,10 +15,12 @@ import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Map; +import net.opentsdb.core.AppendDataPoints; import org.hbase.async.DeleteRequest; import org.hbase.async.HBaseClient; import org.hbase.async.KeyValue; @@ -180,7 +182,7 @@ private static void formatKeyValue(final StringBuilder buf, final byte[] value = kv.value(); final int q_len = qualifier.length; - if (q_len % 2 != 0) { + if (q_len != 3 && q_len % 2 != 0) { if (!importformat) { // custom data object, not a data point if (kv.qualifier()[0] == Annotation.PREFIX()) { @@ -203,8 +205,16 @@ private static void formatKeyValue(final StringBuilder buf, appendImportCell(buf, cell, base_time, tags); } } else { - // compacted column - final ArrayList cells = Internal.extractDataPoints(kv); + final Collection cells; + if (q_len == 3) { + // append data points + final AppendDataPoints adps = new AppendDataPoints(); + cells = adps.parseKeyValue(tsdb, kv); + } else { + // compacted column + cells = Internal.extractDataPoints(kv); + } + if (!importformat) { buf.append(Arrays.toString(kv.qualifier())) .append('\t') diff --git a/test/tools/TestDumpSeries.java b/test/tools/TestDumpSeries.java index 375f3c4710..a589b34068 100644 --- a/test/tools/TestDumpSeries.java +++ b/test/tools/TestDumpSeries.java @@ -23,6 +23,7 @@ import java.lang.reflect.Method; import java.util.HashMap; +import net.opentsdb.core.AppendDataPoints; import net.opentsdb.core.TSDB; import net.opentsdb.meta.Annotation; import net.opentsdb.storage.MockBase; @@ -312,7 +313,18 @@ public void dumpImportCompacted() throws Exception { assertEquals("sys.cpu.user 1356998400004 6 host=web01", log_lines[1]); assertEquals("sys.cpu.user 1356998400008 5 host=web01", log_lines[2]); } - + + @Test + public void dumpImportAppendDataPoints() throws Exception { + writeAppendDataPoints(); + doDump.invoke(null, tsdb, client, "tsdb".getBytes(MockBase.ASCII()), false, + true, new String[] { "1356998400", "1357002000", "sum", "sys.cpu.user" }); + final String[] log_lines = buffer.toString("ISO-8859-1").split("\n"); + assertNotNull(log_lines); + assertEquals("sys.cpu.user 1356998402 42 host=web01", log_lines[0]); + assertEquals("sys.cpu.user 1356998404 6 host=web01", log_lines[1]); + } + @Test public void dumpRawCompactedAndDelete() throws Exception { writeCompactedData(); @@ -397,4 +409,11 @@ private void writeCompactedData() throws Exception { // kvs.add(makekv(qual12, MockBase.concatByteArrays(val1, val2, ZERO))); } + + private void writeAppendDataPoints() throws Exception { + storage.addColumn(MockBase.stringToBytes("00000150E22700000001000001"), + "t".getBytes(MockBase.ASCII()), + AppendDataPoints.APPEND_COLUMN_QUALIFIER, + new byte[] { 0, 0x20, 42, 0, 0x40, 6 }); + } } From 79c54031aa4b95d2a466dba3e5c952e4cabc202e Mon Sep 17 00:00:00 2001 From: CHOE JUNGYEON Date: Tue, 24 May 2016 18:00:51 +0900 Subject: [PATCH 204/233] DumpSeries doesn't know AppendDataPoints - fixed UT failures --- src/core/AppendDataPoints.java | 5 +++++ src/tools/DumpSeries.java | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/core/AppendDataPoints.java b/src/core/AppendDataPoints.java index 8081a507c3..4647ff4b23 100644 --- a/src/core/AppendDataPoints.java +++ b/src/core/AppendDataPoints.java @@ -252,4 +252,9 @@ public byte[] value() { public Deferred repairedDeferred() { return repaired_deferred; } + + /** @return whether or not a qualifier of AppendDataPoints */ + public static boolean isAppendDataPoints(byte[] qualifier) { + return qualifier != null && qualifier.length == 3 && qualifier[0] == APPEND_COLUMN_PREFIX; + } } diff --git a/src/tools/DumpSeries.java b/src/tools/DumpSeries.java index 4bb1887857..ac710878fd 100644 --- a/src/tools/DumpSeries.java +++ b/src/tools/DumpSeries.java @@ -182,7 +182,7 @@ private static void formatKeyValue(final StringBuilder buf, final byte[] value = kv.value(); final int q_len = qualifier.length; - if (q_len != 3 && q_len % 2 != 0) { + if (!AppendDataPoints.isAppendDataPoints(qualifier) && q_len % 2 != 0) { if (!importformat) { // custom data object, not a data point if (kv.qualifier()[0] == Annotation.PREFIX()) { From 1409b550ae7f7fcf9c6cbe7e0dbf59c7363618ad Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Thu, 2 Jun 2016 11:43:48 -0700 Subject: [PATCH 205/233] Fix #794 by making sure the group bys and row key literals list is not null before checking if it's empty. Signed-off-by: Chris Larsen --- src/query/QueryUtil.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/query/QueryUtil.java b/src/query/QueryUtil.java index 994ce86214..1a61c45a0a 100644 --- a/src/query/QueryUtil.java +++ b/src/query/QueryUtil.java @@ -200,7 +200,8 @@ public static void setDataTableScanFilter( final int end_time) { // no-op - if (group_bys.isEmpty() && row_key_literals.isEmpty()) { + if ((group_bys == null || group_bys.isEmpty()) + && (row_key_literals == null || row_key_literals.isEmpty())) { return; } From d3ecb5330419598dc216b5c7d9f8e87842141bbc Mon Sep 17 00:00:00 2001 From: "Peter (Stig) Edwards" Date: Thu, 7 Jul 2016 16:52:07 +0100 Subject: [PATCH 206/233] Use thread safe ConcurrentHashMap. (#3) For https://github.com/OpenTSDB/opentsdb/issues/823 Concurrent use of a HashSet can trigger race conditions and an infinite loop(s), use the thread-safe ConcurrentHashMap to avoid this. Making the change just for the thread stack seen looping. Signed-off-by: Chris Larsen --- src/core/SaltScanner.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/core/SaltScanner.java b/src/core/SaltScanner.java index 3a929cd2af..ce4e433c1a 100644 --- a/src/core/SaltScanner.java +++ b/src/core/SaltScanner.java @@ -15,7 +15,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -306,8 +305,8 @@ final class ScannerCB implements Callback kvs = new ArrayList(); private final ByteMap> annotations = new ByteMap>(); - private final Set skips = new HashSet(); - private final Set keepers = new HashSet(); + private final Set skips = Collections.newSetFromMap(new ConcurrentHashMap()); + private final Set keepers = Collections.newSetFromMap(new ConcurrentHashMap()); private long scanner_start = -1; /** nanosecond timestamps */ From ad5b28986d4e44e8f95549ab3f3fe9c04a6ba15b Mon Sep 17 00:00:00 2001 From: Kevin Landreth Date: Wed, 13 Jul 2016 18:25:38 +0000 Subject: [PATCH 207/233] Log X-Forwarded-For address when handling HTTP requests --- src/tsd/AbstractHttpQuery.java | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/tsd/AbstractHttpQuery.java b/src/tsd/AbstractHttpQuery.java index 967eaab09f..035404a5ad 100644 --- a/src/tsd/AbstractHttpQuery.java +++ b/src/tsd/AbstractHttpQuery.java @@ -467,21 +467,37 @@ protected Logger logger() { return LOG; } + protected final String logChannel() { + if (request.containsHeader("X-Forwarded-For")) { + String inetAddress; + String proxyChain = request.getHeader("X-Forwarded-For"); + int firstComma = proxyChain.indexOf(','); + if (firstComma != -1) { + inetAddress = proxyChain.substring(0, proxyChain.indexOf(',')); + } else { + inetAddress = proxyChain; + } + return "[id: 0x" + Integer.toHexString(chan.hashCode()) + ", /" + inetAddress + " => " + chan.getLocalAddress() + ']'; + } else { + return chan.toString(); + } + } + protected final void logInfo(final String msg) { if (logger().isInfoEnabled()) { - logger().info(chan.toString() + ' ' + msg); + logger().info(logChannel() + ' ' + msg); } } protected final void logWarn(final String msg) { if (logger().isWarnEnabled()) { - logger().warn(chan.toString() + ' ' + msg); + logger().warn(logChannel() + ' ' + msg); } } protected final void logError(final String msg, final Exception e) { if (logger().isErrorEnabled()) { - logger().error(chan.toString() + ' ' + msg, e); + logger().error(logChannel() + ' ' + msg, e); } } From 4b906946be8b934c12d8b1cb53c91ec22f1d275e Mon Sep 17 00:00:00 2001 From: Kevin Landreth Date: Fri, 29 Jul 2016 22:50:05 -0500 Subject: [PATCH 208/233] fix ALPN version comparisons in include.mk file --- third_party/alpn-boot/include.mk | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/third_party/alpn-boot/include.mk b/third_party/alpn-boot/include.mk index 83ff6792bd..a949d1da29 100644 --- a/third_party/alpn-boot/include.mk +++ b/third_party/alpn-boot/include.mk @@ -27,25 +27,25 @@ ALPN_BOOT_VERSION = $(shell version= ;\ minor=$${BASH_REMATCH[2]}; \ sub=$${BASH_REMATCH[3]}; \ if [[ $$major = "1.7" ]]; then \ - if [[ $$sub < 71 ]]; then \ + if [[ $$sub -lt 71 ]]; then \ echo "7.1.0.v20141016"; \ - elif [[ $$sub < 75 ]]; then \ + elif [[ $$sub -lt 75 ]]; then \ echo "7.1.2.v20141202"; \ else \ echo "7.1.3.v20150130"; \ fi \ elif [[ $$major = "1.8" ]]; then \ - if [[ $$sub < 25 ]]; then \ + if [[ $$sub -lt 25 ]]; then \ echo "8.1.0.v20141016"; \ - elif [[ $$sub < 31 ]]; then \ + elif [[ $$sub -lt 31 ]]; then \ echo "8.1.2.v20141202"; \ - elif [[ $$sub < 51 ]]; then \ + elif [[ $$sub -lt 51 ]]; then \ echo "8.1.3.v20150130"; \ - elif [[ $$sub < 60 ]]; then \ + elif [[ $$sub -lt 60 ]]; then \ echo "8.1.4.v20150727"; \ - elif [[ $$sub < 65 ]]; then \ + elif [[ $$sub -lt 65 ]]; then \ echo "8.1.5.v20150921"; \ - elif [[ $$sub < 71 ]]; then \ + elif [[ $$sub -lt 71 ]]; then \ echo "8.1.6.v20151105"; \ else \ echo "8.1.7.v20160121"; \ From 2d35e21cff82cbd75609635b2e8a8550b72461b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?maxmeng=28=E8=92=99=E5=8D=93=29?= Date: Mon, 5 Sep 2016 16:12:35 +0800 Subject: [PATCH 209/233] add bad percent fix #728 --- tools/check_tsd | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/check_tsd b/tools/check_tsd index ecc42b0551..0159db4d82 100755 --- a/tools/check_tsd +++ b/tools/check_tsd @@ -81,6 +81,9 @@ def main(argv): parser.add_option('-N', '--now', type='int', default=None, metavar='UTC', help='Set unix timestamp for "now", for testing') + parser.add_option('-B', '--bad_percent', dest='bad_percent', default=None, + metavar='PERCENT', type='float', help='Ignore alarm if PERCENT of the data' + ' points is bad') parser.add_option('-S', '--ssl', default=False, action='store_true', help='Make queries to OpenTSDB via SSL (https)') (options, args) = parser.parse_args(args=argv[1:]) From 898c17b54d32b97c16ca0a807ebac8fa4abda29a Mon Sep 17 00:00:00 2001 From: Jonathan Creasy Date: Tue, 6 Sep 2016 15:01:53 -0500 Subject: [PATCH 210/233] Fixes #855, bumped Zookeeper version to 3.4.6 --- third_party/zookeeper/include.mk | 2 +- third_party/zookeeper/zookeeper-3.4.6.jar.md5 | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 third_party/zookeeper/zookeeper-3.4.6.jar.md5 diff --git a/third_party/zookeeper/include.mk b/third_party/zookeeper/include.mk index 514b9dc5ed..7fc7695f4f 100644 --- a/third_party/zookeeper/include.mk +++ b/third_party/zookeeper/include.mk @@ -13,7 +13,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see . -ZOOKEEPER_VERSION := 3.4.5 +ZOOKEEPER_VERSION := 3.4.6 ZOOKEEPER := third_party/zookeeper/zookeeper-$(ZOOKEEPER_VERSION).jar ZOOKEEPER_BASE_URL := http://central.maven.org/maven2/org/apache/zookeeper/zookeeper/$(ZOOKEEPER_VERSION) diff --git a/third_party/zookeeper/zookeeper-3.4.6.jar.md5 b/third_party/zookeeper/zookeeper-3.4.6.jar.md5 new file mode 100644 index 0000000000..ce5652c709 --- /dev/null +++ b/third_party/zookeeper/zookeeper-3.4.6.jar.md5 @@ -0,0 +1 @@ +7d01d317c717268725896cfb81b18152 \ No newline at end of file From 15a9839407411a3fd3ac43c0a88a587c8f62bc39 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sat, 17 Sep 2016 15:19:26 -0700 Subject: [PATCH 211/233] Move to AsyncHBase 1.7.2 Signed-off-by: Chris Larsen --- third_party/hbase/asynchbase-1.7.0.jar.md5 | 1 - third_party/hbase/asynchbase-1.7.2.jar.md5 | 1 + third_party/hbase/include.mk | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 third_party/hbase/asynchbase-1.7.0.jar.md5 create mode 100644 third_party/hbase/asynchbase-1.7.2.jar.md5 diff --git a/third_party/hbase/asynchbase-1.7.0.jar.md5 b/third_party/hbase/asynchbase-1.7.0.jar.md5 deleted file mode 100644 index 5cb6466209..0000000000 --- a/third_party/hbase/asynchbase-1.7.0.jar.md5 +++ /dev/null @@ -1 +0,0 @@ -f1aed41b7f16345d2f58797ffa77f36a \ No newline at end of file diff --git a/third_party/hbase/asynchbase-1.7.2.jar.md5 b/third_party/hbase/asynchbase-1.7.2.jar.md5 new file mode 100644 index 0000000000..df83960284 --- /dev/null +++ b/third_party/hbase/asynchbase-1.7.2.jar.md5 @@ -0,0 +1 @@ +35fdde5a8e6009553e6aab5357ed8896 \ No newline at end of file diff --git a/third_party/hbase/include.mk b/third_party/hbase/include.mk index bb25220311..7cb693bb8a 100644 --- a/third_party/hbase/include.mk +++ b/third_party/hbase/include.mk @@ -13,7 +13,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see . -ASYNCHBASE_VERSION := 1.7.1 +ASYNCHBASE_VERSION := 1.7.2 ASYNCHBASE := third_party/hbase/asynchbase-$(ASYNCHBASE_VERSION).jar ASYNCHBASE_BASE_URL := http://central.maven.org/maven2/org/hbase/asynchbase/$(ASYNCHBASE_VERSION) From dcffb36cb41f21368423661e420be8e3b4ee0f80 Mon Sep 17 00:00:00 2001 From: Jonathan Creasy Date: Wed, 6 Jul 2016 21:31:08 -0500 Subject: [PATCH 212/233] Added better error handling and gracefully handle NPE Fixes #817 Signed-off-by: Chris Larsen --- src/tsd/QueryExecutor.java | 122 ++++++++++++++++++++++++------------- 1 file changed, 79 insertions(+), 43 deletions(-) diff --git a/src/tsd/QueryExecutor.java b/src/tsd/QueryExecutor.java index 22937fcb4b..c618c939e7 100644 --- a/src/tsd/QueryExecutor.java +++ b/src/tsd/QueryExecutor.java @@ -12,6 +12,7 @@ // see . package net.opentsdb.tsd; +import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collections; @@ -277,72 +278,115 @@ public Object call(final ArrayList query_results) } } } - + // handle nested expressions - DirectedAcyclicGraph graph = null; + DirectedAcyclicGraph graph = new DirectedAcyclicGraph(DefaultEdge.class); + for (final Entry eii : expressions.entrySet()) { + LOG.debug(String.format("Expression entry key is %s, value is %s", eii.getKey(), eii.getValue().toString())); + LOG.debug(String.format("Time to loop through the variable names for %s", eii.getKey())); + + if (!graph.containsVertex(eii.getKey())) { + LOG.debug("Adding vertex " + eii.getKey()); + graph.addVertex(eii.getKey()); + } + for (final String var : eii.getValue().getVariableNames()) { + LOG.debug(String.format("var is %s", var)); + final ExpressionIterator ei = expressions.get(var); + if (ei != null) { + LOG.debug(String.format("The expression iterator for %s is %s", var, ei.toString())); + // TODO - really ought to calculate this earlier if (eii.getKey().equals(var)) { throw new IllegalArgumentException( "Self referencing expression found: " + eii.getKey()); } + LOG.debug("Nested expression detected. " + eii.getKey() + " depends on " + var); - if (graph == null) { - graph = new DirectedAcyclicGraph(DefaultEdge.class); - } if (!graph.containsVertex(eii.getKey())) { + LOG.debug("Added vertex " + eii.getKey()); graph.addVertex(eii.getKey()); + } else { + LOG.debug("Already contains vertex " + eii.getKey()); } + if (!graph.containsVertex(var)) { + LOG.debug("Added vertex " + var); graph.addVertex(var); + } else { + LOG.debug("Already contains vertex " + var); } + try { + LOG.debug("Added Edge " + eii.getKey() + " - " + var); graph.addDagEdge(eii.getKey(), var); } catch (CycleFoundException cfe) { throw new IllegalArgumentException("Circular reference found: " + eii.getKey(), cfe); } + } else { + LOG.debug(String.format("The expression iterator for %s is null", var)); } } } // compile all of the expressions final long intersect_start = DateTime.currentTimeMillis(); - if (graph != null) { - final ExpressionIterator[] compile_stack = - new ExpressionIterator[expressions.size()]; - final TopologicalOrderIterator it = - new TopologicalOrderIterator(graph); - int i = 0; - while (it.hasNext()) { - compile_stack[i++] = expressions.get(it.next()); + + if (graph == null) { + throw new IOException("Internal Error: graph cannot be null"); + } + + final Integer expressionLength = expressions.size(); + final ExpressionIterator[] compile_stack = new ExpressionIterator[expressionLength]; + final TopologicalOrderIterator it = new TopologicalOrderIterator(graph); + + LOG.debug(String.format("Expressions Size is %d", expressionLength)); + LOG.debug(String.format("Topology Iterator %s", it.toString())); + + int i = 0; + while (it.hasNext()) { + String next = it.next(); + LOG.debug(String.format("Expression: %s", next)); + ExpressionIterator ei = expressions.get(next); + LOG.debug(String.format("Expression Iterator: %s", ei.toString())); + if (ei == null) { + LOG.error(String.format("The expression iterator for %s is null", next)); } - for (int x = compile_stack.length - 1; x >= 0; x--) { - // look for and add expressions - for (final String var : compile_stack[x].getVariableNames()) { - ExpressionIterator source = expressions.get(var); - if (source != null) { - compile_stack[x].addResults(var, source.getCopy()); - LOG.debug("Adding expression " + source.getId() + " to " + - compile_stack[x].getId()); - } - } - - compile_stack[x].compile(); - LOG.debug("Successfully compiled " + compile_stack[x]); + compile_stack[i] = ei; + LOG.debug(String.format("Added expression %s to compile_stack[%d]", next, i)); + i++; + } + + if (i != expressionLength) { + throw new IOException(String.format(" Internal Error: Less expressions where added to the compile stack than expressions.size (%d instead of %d)", i, expressionLength)); + } + + LOG.debug(String.format("compile stack length: %d", compile_stack.length)); + + for (int x = compile_stack.length - 1; x >= 0; x--) { + if (compile_stack[x] == null) { + throw new NullPointerException(String.format("Item %d in compile_stack[] is null", x)); } - } else { - for (final ExpressionIterator ei : expressions.values()) { - ei.compile(); - LOG.debug("Successfully compiled " + ei); + // look for and add expressions + for (final String var : compile_stack[x].getVariableNames()) { + LOG.debug(String.format("Looking for variable %s for %s", var, compile_stack[x].getId())); + ExpressionIterator source = expressions.get(var); + if (source != null) { + compile_stack[x].addResults(var, source.getCopy()); + LOG.debug(String.format("Adding expression %s to %s", source.getId(), compile_stack[x].getId())); + } } + compile_stack[x].compile(); + LOG.debug(String.format("Successfully compiled %s", compile_stack[x].getId())); } - LOG.debug("Finished compilations in " + + + LOG.debug("Finished compilations in " + (DateTime.currentTimeMillis() - intersect_start) + " ms"); return serialize().addCallback(new CompleteCB()).addErrback(new ErrorCB()); @@ -498,30 +542,22 @@ public Object call(final Exception e) throws Exception { if (ex != null) { LOG.error("Unexpected exception: ", ex); // TODO - find a better way to determine the real error -// QueryExecutor.this.ts_query.getQueryStats() -// .markComplete(HttpResponseStatus.BAD_REQUEST, ex); QueryExecutor.this.http_query.badRequest(new BadRequestException(ex)); } else { LOG.error("The deferred group exception didn't have a cause???"); -// QueryExecutor.this.ts_query.getQueryStats() -// .markComplete(HttpResponseStatus.INTERNAL_SERVER_ERROR, ex); QueryExecutor.this.http_query.badRequest(new BadRequestException(e)); } } else if (e.getClass() == QueryException.class) { -// QueryExecutor.this.ts_query.getQueryStats() -// .markComplete(HttpResponseStatus.REQUEST_TIMEOUT, e); - QueryExecutor.this.http_query.badRequest(new BadRequestException((QueryException)e)); + QueryExecutor.this.http_query.badRequest(new BadRequestException((QueryException) e)); + } else if ((e instanceof IOException) || (e instanceof NullPointerException) || (e instanceof RuntimeException)) { + QueryExecutor.this.http_query.internalError(e); } else { -// QueryExecutor.this.ts_query.getQueryStats() -// .markComplete(HttpResponseStatus.INTERNAL_SERVER_ERROR, e); QueryExecutor.this.http_query.badRequest(new BadRequestException(e)); } return null; } catch (RuntimeException ex) { LOG.error("Exception thrown during exception handling", ex); -// QueryExecutor.this.ts_query.getQueryStats() -// .markComplete(HttpResponseStatus.INTERNAL_SERVER_ERROR, ex); - QueryExecutor.this.http_query.sendReply(HttpResponseStatus.INTERNAL_SERVER_ERROR, + QueryExecutor.this.http_query.sendReply(HttpResponseStatus.INTERNAL_SERVER_ERROR, ex.getMessage().getBytes()); return null; } From 1a9f1fdfba13c09513e3e223aec66f18828d11ab Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sat, 17 Sep 2016 17:29:14 -0700 Subject: [PATCH 213/233] Properly validate expression queries before passing them to the queryExecutor. Wrap debug log lines in the query executor so they don't affect performance when not in debug mode. Filters are now optional in the expression queries. Signed-off-by: Chris Larsen --- src/query/expression/ExpressionIterator.java | 12 +- src/query/pojo/Expression.java | 39 ++++ src/query/pojo/Query.java | 44 +++-- src/tsd/QueryExecutor.java | 183 ++++++++++++------- test/query/pojo/TestQuery.java | 4 +- test/tsd/TestQueryExecutor.java | 106 ++++++++++- 6 files changed, 299 insertions(+), 89 deletions(-) diff --git a/src/query/expression/ExpressionIterator.java b/src/query/expression/ExpressionIterator.java index 2e01585657..3b33d5678b 100644 --- a/src/query/expression/ExpressionIterator.java +++ b/src/query/expression/ExpressionIterator.java @@ -39,7 +39,7 @@ * - Instantiate with a valid expression * - Call {@link #getVariableNames()} and iterate over a set of TSSubQueries and * their results. For each query that matches a variable name, call - * {@link #addResults()} with the result set. + * {@link #addResults(String, ITimeSyncedIterator)} with the result set. * - Call {@link #compile()} to setup the meta data, fills and compute the * intersection of the series. * - Call {@link #values()} and store the reference. Results for each @@ -67,7 +67,7 @@ public class ExpressionIterator implements ITimeSyncedIterator { * as not thread safe, so I assume it's ok to instantiate one of these guys * and keep creating scripts from it. */ - private final static JexlEngine JEXL_ENGINE = new JexlEngine(); + public final static JexlEngine JEXL_ENGINE = new JexlEngine(); /** Whether or not to intersect on the query tagks instead of the result set * tagks */ @@ -114,6 +114,7 @@ public class ExpressionIterator implements ITimeSyncedIterator { // no tagk filters then we shouldn't set the II's intersect_on_query_tagks /** * Default Ctor that compiles the expression for use with this iterator. + * @param id The id of this iterator. * @param expression The expression to compile and use * @param set_operator The type of set operator to use * @param intersect_on_query_tagks Whether or not to include only the query @@ -204,7 +205,8 @@ public String toString() { /** * Adds a sub query result object to the iterator. * TODO - accept a proper object, not a map - * @param results The results to store. + * @param id The ID of source iterator. + * @param iterator The source iterator. * @throws IllegalArgumentException if the object is missing required data */ public void addResults(final String id, final ITimeSyncedIterator iterator) { @@ -235,8 +237,8 @@ public void compile() { } if (results.size() < names.size()) { throw new IllegalArgumentException("Not enough query results [" - + results.size() + "] for the expression variables [" - + names.size() + "] " + this); + + results.size() + " total results found] for the expression variables [" + + names.size() + " expected] " + this); } // don't care if we have extra results, but we had darned well better make diff --git a/src/query/pojo/Expression.java b/src/query/pojo/Expression.java index 9f5aaebdf5..0b313d9835 100644 --- a/src/query/pojo/Expression.java +++ b/src/query/pojo/Expression.java @@ -12,9 +12,17 @@ // see . package net.opentsdb.query.pojo; +import net.opentsdb.query.expression.ExpressionIterator; import net.opentsdb.query.expression.NumericFillPolicy; import net.opentsdb.query.expression.VariableIterator.SetOperator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.jexl2.Script; + +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -40,6 +48,12 @@ public class Expression extends Validatable { /** The fill policy to use for ? */ private NumericFillPolicy fill_policy; + /** Set of unique variables used by this expression. */ + private Set variables; + + /** The parsed expression via JEXL. */ + private Script parsed_expression; + /** * Default ctor * @param builder The builder to pull values from @@ -90,11 +104,36 @@ public void validate() { throw new IllegalArgumentException("missing or empty expr"); } + // parse it just to make sure we're happy and extract the variable names. + // Will throw JexlException + parsed_expression = ExpressionIterator.JEXL_ENGINE.createScript(expr); + variables = new HashSet(); + for (final List exp_list : + ExpressionIterator.JEXL_ENGINE.getVariables(parsed_expression)) { + for (final String variable : exp_list) { + variables.add(variable); + } + } + // others are optional if (join == null) { join = Join.Builder().setOperator(SetOperator.UNION).build(); } } + + /** @return The parsed expression. May be null if {@link validate} has not + * been called yet. */ + @JsonIgnore + public Script getParsedExpression() { + return parsed_expression; + } + + /** @return A set of unique variables for the expression. May be null if + * {@link validate} has not been called yet. */ + @JsonIgnore + public Set getVariables() { + return variables; + } @Override public boolean equals(final Object o) { diff --git a/src/query/pojo/Query.java b/src/query/pojo/Query.java index 09f62dcf78..731c449950 100644 --- a/src/query/pojo/Query.java +++ b/src/query/pojo/Query.java @@ -18,6 +18,8 @@ import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; import com.google.common.base.Objects; +import net.opentsdb.utils.JSON; + import java.util.HashSet; import java.util.List; import java.util.Set; @@ -109,14 +111,13 @@ public void validate() { throw new IllegalArgumentException("missing or empty metrics"); } - final Set metric_ids = new HashSet(); - + final Set variable_ids = new HashSet(); for (Metric metric : metrics) { - if (metric_ids.contains(metric.getId())) { + if (variable_ids.contains(metric.getId())) { throw new IllegalArgumentException("duplicated metric id: " + metric.getId()); } - metric_ids.add(metric.getId()); + variable_ids.add(metric.getId()); } final Set filter_ids = new HashSet(); @@ -128,15 +129,13 @@ public void validate() { } filter_ids.add(filter.getId()); } - - final Set expression_ids = new HashSet(); - + for (Expression expression : expressions) { - if (expression_ids.contains(expression.getId())) { - throw new IllegalArgumentException("duplicated expression id: " + if (variable_ids.contains(expression.getId())) { + throw new IllegalArgumentException("Duplicated variable or expression id: " + expression.getId()); } - expression_ids.add(expression.getId()); + variable_ids.add(expression.getId()); } validateCollection(metrics, "metric"); @@ -150,19 +149,38 @@ public void validate() { } validateFilters(); + + if (expressions != null) { + validateCollection(expressions, "expression"); + for (final Expression exp : expressions) { + if (exp.getVariables() == null) { + throw new IllegalArgumentException("No variables found for an " + + "expression?! " + JSON.serializeToString(exp)); + } + + for (final String var : exp.getVariables()) { + if (!variable_ids.contains(var)) { + throw new IllegalArgumentException("Expression [" + exp.getExpr() + + "] was missing input " + var); + } + } + } + } } /** Validates the filters, making sure each metric has a filter * @throws IllegalArgumentException if one or more parameters were invalid */ private void validateFilters() { - final Set ids = new HashSet(); + Set ids = new HashSet(); for (Filter filter : filters) { ids.add(filter.getId()); } - for (Metric metric : metrics) { - if (!ids.contains(metric.getFilter())) { + for(Metric metric : metrics) { + if (metric.getFilter() != null && + !metric.getFilter().isEmpty() && + !ids.contains(metric.getFilter())) { throw new IllegalArgumentException( String.format("unrecognized filter id %s in metric %s", metric.getFilter(), metric.getId())); diff --git a/src/tsd/QueryExecutor.java b/src/tsd/QueryExecutor.java index c618c939e7..b0598241e2 100644 --- a/src/tsd/QueryExecutor.java +++ b/src/tsd/QueryExecutor.java @@ -1,5 +1,5 @@ // This file is part of OpenTSDB. -// Copyright (C) 2010-2012 The OpenTSDB Authors. +// Copyright (C) 2010-2016 The OpenTSDB Authors. // // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published by @@ -22,6 +22,8 @@ import java.util.Map; import java.util.Map.Entry; +import org.hbase.async.HBaseException; +import org.hbase.async.RpcTimedOutException; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBufferOutputStream; import org.jboss.netty.buffer.ChannelBuffers; @@ -58,6 +60,7 @@ import net.opentsdb.query.pojo.Query; import net.opentsdb.query.pojo.Timespan; import net.opentsdb.stats.QueryStats; +import net.opentsdb.uid.NoSuchUniqueName; import net.opentsdb.uid.UniqueId.UniqueIdType; import net.opentsdb.utils.DateTime; import net.opentsdb.utils.JSON; @@ -175,14 +178,15 @@ public QueryExecutor(final TSDB tsdb, final Query query) { break; } } - sub.setRate(timespan.isRate()); - sub.setAggregator( - mq.getAggregator() != null ? mq.getAggregator() : timespan.getAggregator()); if (filters != null) { sub.setFilters(filters); sub.setExplicitTags(explicit_tags); } } + + sub.setRate(timespan.isRate()); + sub.setAggregator( + mq.getAggregator() != null ? mq.getAggregator() : timespan.getAggregator()); } final ArrayList subs = @@ -220,9 +224,7 @@ public void execute(final HttpQuery query) { final QueryStats query_stats = new QueryStats(query.getRemoteAddress(), ts_query, query.getHeaders()); ts_query.setQueryStats(query_stats); - - final long start = DateTime.currentTimeMillis(); - + /** * Sends the serialized results to the caller. This should be the very * last callback executed. @@ -271,8 +273,10 @@ public Object call(final ArrayList query_results) tsi.setFillPolicy(fill); } ei.addResults(entry.getKey(), tsi); - LOG.debug("Added results for " + entry.getKey() + - " to " + ei.getId()); + if (LOG.isDebugEnabled()) { + LOG.debug("Added results for " + entry.getKey() + + " to " + ei.getId()); + } } } } @@ -280,24 +284,36 @@ public Object call(final ArrayList query_results) } // handle nested expressions - DirectedAcyclicGraph graph = new DirectedAcyclicGraph(DefaultEdge.class); + final DirectedAcyclicGraph graph = + new DirectedAcyclicGraph(DefaultEdge.class); for (final Entry eii : expressions.entrySet()) { - LOG.debug(String.format("Expression entry key is %s, value is %s", eii.getKey(), eii.getValue().toString())); - LOG.debug(String.format("Time to loop through the variable names for %s", eii.getKey())); + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Expression entry key is %s, value is %s", + eii.getKey(), eii.getValue().toString())); + LOG.debug(String.format("Time to loop through the variable names " + + "for %s", eii.getKey())); + } if (!graph.containsVertex(eii.getKey())) { - LOG.debug("Adding vertex " + eii.getKey()); + if (LOG.isDebugEnabled()) { + LOG.debug("Adding vertex " + eii.getKey()); + } graph.addVertex(eii.getKey()); } for (final String var : eii.getValue().getVariableNames()) { - LOG.debug(String.format("var is %s", var)); + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("var is %s", var)); + } final ExpressionIterator ei = expressions.get(var); if (ei != null) { - LOG.debug(String.format("The expression iterator for %s is %s", var, ei.toString())); + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("The expression iterator for %s is %s", + var, ei.toString())); + } // TODO - really ought to calculate this earlier if (eii.getKey().equals(var)) { @@ -305,31 +321,39 @@ public Object call(final ArrayList query_results) "Self referencing expression found: " + eii.getKey()); } - LOG.debug("Nested expression detected. " + eii.getKey() + - " depends on " + var); + if (LOG.isDebugEnabled()) { + LOG.debug("Nested expression detected. " + eii.getKey() + + " depends on " + var); + } if (!graph.containsVertex(eii.getKey())) { - LOG.debug("Added vertex " + eii.getKey()); + if (LOG.isDebugEnabled()) { + LOG.debug("Added vertex " + eii.getKey()); + } graph.addVertex(eii.getKey()); - } else { + } else if (LOG.isDebugEnabled()) { LOG.debug("Already contains vertex " + eii.getKey()); } if (!graph.containsVertex(var)) { - LOG.debug("Added vertex " + var); + if (LOG.isDebugEnabled()) { + LOG.debug("Added vertex " + var); + } graph.addVertex(var); - } else { + } else if (LOG.isDebugEnabled()) { LOG.debug("Already contains vertex " + var); } try { - LOG.debug("Added Edge " + eii.getKey() + " - " + var); + if (LOG.isDebugEnabled()) { + LOG.debug("Added Edge " + eii.getKey() + " - " + var); + } graph.addDagEdge(eii.getKey(), var); } catch (CycleFoundException cfe) { throw new IllegalArgumentException("Circular reference found: " + eii.getKey(), cfe); } - } else { + } else if (LOG.isDebugEnabled()) { LOG.debug(String.format("The expression iterator for %s is null", var)); } } @@ -338,58 +362,83 @@ public Object call(final ArrayList query_results) // compile all of the expressions final long intersect_start = DateTime.currentTimeMillis(); - if (graph == null) { - throw new IOException("Internal Error: graph cannot be null"); - } - final Integer expressionLength = expressions.size(); - final ExpressionIterator[] compile_stack = new ExpressionIterator[expressionLength]; - final TopologicalOrderIterator it = new TopologicalOrderIterator(graph); - - LOG.debug(String.format("Expressions Size is %d", expressionLength)); - LOG.debug(String.format("Topology Iterator %s", it.toString())); + final ExpressionIterator[] compile_stack = + new ExpressionIterator[expressionLength]; + final TopologicalOrderIterator it = + new TopologicalOrderIterator(graph); + + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Expressions Size is %d", expressionLength)); + LOG.debug(String.format("Topology Iterator %s", it.toString())); + } int i = 0; while (it.hasNext()) { String next = it.next(); - LOG.debug(String.format("Expression: %s", next)); + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Expression: %s", next)); + } ExpressionIterator ei = expressions.get(next); - LOG.debug(String.format("Expression Iterator: %s", ei.toString())); + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Expression Iterator: %s", ei.toString())); + } if (ei == null) { LOG.error(String.format("The expression iterator for %s is null", next)); } compile_stack[i] = ei; - LOG.debug(String.format("Added expression %s to compile_stack[%d]", next, i)); + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Added expression %s to compile_stack[%d]", + next, i)); + } i++; } if (i != expressionLength) { - throw new IOException(String.format(" Internal Error: Less expressions where added to the compile stack than expressions.size (%d instead of %d)", i, expressionLength)); + throw new IOException(String.format(" Internal Error: Fewer " + + "expressions where added to the compile stack than " + + "expressions.size (%d instead of %d)", i, expressionLength)); } - LOG.debug(String.format("compile stack length: %d", compile_stack.length)); + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("compile stack length: %d", compile_stack.length)); + } for (int x = compile_stack.length - 1; x >= 0; x--) { if (compile_stack[x] == null) { - throw new NullPointerException(String.format("Item %d in compile_stack[] is null", x)); + throw new NullPointerException(String.format("Item %d in " + + "compile_stack[] is null", x)); } // look for and add expressions for (final String var : compile_stack[x].getVariableNames()) { - LOG.debug(String.format("Looking for variable %s for %s", var, compile_stack[x].getId())); + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Looking for variable %s for %s", var, + compile_stack[x].getId())); + } ExpressionIterator source = expressions.get(var); if (source != null) { compile_stack[x].addResults(var, source.getCopy()); - LOG.debug(String.format("Adding expression %s to %s", source.getId(), compile_stack[x].getId())); + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Adding expression %s to %s", + source.getId(), compile_stack[x].getId())); + } } } compile_stack[x].compile(); - LOG.debug(String.format("Successfully compiled %s", compile_stack[x].getId())); + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Successfully compiled %s", + compile_stack[x].getId())); + } } - LOG.debug("Finished compilations in " + - (DateTime.currentTimeMillis() - intersect_start) + " ms"); + if (LOG.isDebugEnabled()) { + LOG.debug("Finished compilations in " + + (DateTime.currentTimeMillis() - intersect_start) + " ms"); + } - return serialize().addCallback(new CompleteCB()).addErrback(new ErrorCB()); + return serialize() + .addCallback(new CompleteCB()) + .addErrback(new ErrorCB()); } } @@ -414,7 +463,8 @@ public Deferred call(final net.opentsdb.core.Query[] queries) { // TODO - only run the ones that will be involved in an output. Folks WILL // ask for stuff they don't need.... *sigh* - ts_query.buildQueriesAsync(tsdb).addCallback(new BuildCB()) + ts_query.buildQueriesAsync(tsdb) + .addCallback(new BuildCB()) .addErrback(new ErrorCB()); } @@ -532,35 +582,42 @@ public ChannelBuffer call(final Object obj) /** This has to be attached to callbacks or we may never respond to clients */ class ErrorCB implements Callback { public Object call(final Exception e) throws Exception { + QueryRpc.query_exceptions.incrementAndGet(); + Throwable ex = e; try { LOG.error("Query exception: ", e); if (e instanceof DeferredGroupException) { - Throwable ex = e.getCause(); + ex = e.getCause(); while (ex != null && ex instanceof DeferredGroupException) { ex = ex.getCause(); } - if (ex != null) { - LOG.error("Unexpected exception: ", ex); - // TODO - find a better way to determine the real error - QueryExecutor.this.http_query.badRequest(new BadRequestException(ex)); - } else { + if (ex == null) { LOG.error("The deferred group exception didn't have a cause???"); - QueryExecutor.this.http_query.badRequest(new BadRequestException(e)); } - } else if (e.getClass() == QueryException.class) { - QueryExecutor.this.http_query.badRequest(new BadRequestException((QueryException) e)); - } else if ((e instanceof IOException) || (e instanceof NullPointerException) || (e instanceof RuntimeException)) { - QueryExecutor.this.http_query.internalError(e); + } + if (ex instanceof RpcTimedOutException) { + QueryExecutor.this.http_query.badRequest(new BadRequestException( + HttpResponseStatus.REQUEST_TIMEOUT, ex.getMessage())); + } else if (ex instanceof HBaseException) { + QueryExecutor.this.http_query.badRequest(new BadRequestException( + HttpResponseStatus.FAILED_DEPENDENCY, ex.getMessage())); + } else if (ex instanceof QueryException) { + QueryExecutor.this.http_query.badRequest(new BadRequestException( + ((QueryException)ex).getStatus(), ex.getMessage())); + } else if (ex instanceof BadRequestException) { + QueryExecutor.this.http_query.badRequest((BadRequestException)ex); + } else if (ex instanceof NoSuchUniqueName) { + QueryExecutor.this.http_query.badRequest(new BadRequestException(ex)); } else { - QueryExecutor.this.http_query.badRequest(new BadRequestException(e)); + QueryExecutor.this.http_query.badRequest(new BadRequestException(ex)); } - return null; - } catch (RuntimeException ex) { - LOG.error("Exception thrown during exception handling", ex); - QueryExecutor.this.http_query.sendReply(HttpResponseStatus.INTERNAL_SERVER_ERROR, - ex.getMessage().getBytes()); - return null; + + } catch (RuntimeException ex2) { + LOG.error("Exception thrown during exception handling", ex2); + QueryExecutor.this.http_query.sendReply + (HttpResponseStatus.INTERNAL_SERVER_ERROR, ex2.getMessage().getBytes()); } + return null; } } diff --git a/test/query/pojo/TestQuery.java b/test/query/pojo/TestQuery.java index ff8585d9c8..613ff787a8 100644 --- a/test/query/pojo/TestQuery.java +++ b/test/query/pojo/TestQuery.java @@ -70,7 +70,7 @@ public class TestQuery { + " \"expressions\":[" + " {" + " \"id\":\"e1\"," - + " \"expr\":\"a + b + c\"" + + " \"expr\":\"m1 * 1024\"" + " }" + " ]," + " \"outputs\":[" @@ -96,7 +96,7 @@ public void setup() { .setId("m1").setFilter("f1").setTimeOffset("0") .setAggregator("sum").build(); expression = Expression.Builder().setId("e1") - .setExpression("a + b + c").setJoin( + .setExpression("m1 * 1024").setJoin( Join.Builder().setOperator(SetOperator.UNION).build()).build(); output = Output.Builder().setId("m1").setAlias("CPU Idle EAST DC") .build(); diff --git a/test/tsd/TestQueryExecutor.java b/test/tsd/TestQueryExecutor.java index eeabd47b3b..b5c1f5c903 100644 --- a/test/tsd/TestQueryExecutor.java +++ b/test/tsd/TestQueryExecutor.java @@ -1,5 +1,5 @@ // This file is part of OpenTSDB. -// Copyright (C) 2010-2012 The OpenTSDB Authors. +// Copyright (C) 2010-2016 The OpenTSDB Authors. // // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published by @@ -19,11 +19,14 @@ import java.util.Arrays; import java.util.List; +import net.opentsdb.core.FillPolicy; import net.opentsdb.core.TSDB; import net.opentsdb.core.TSQuery; +import net.opentsdb.query.expression.NumericFillPolicy; import net.opentsdb.query.expression.BaseTimeSyncedIteratorTest; import net.opentsdb.query.expression.VariableIterator.SetOperator; import net.opentsdb.query.filter.TagVFilter; +import net.opentsdb.query.pojo.Downsampler; import net.opentsdb.query.pojo.Expression; import net.opentsdb.query.pojo.Filter; import net.opentsdb.query.pojo.Join; @@ -188,6 +191,101 @@ public void oneExpressionDefaultFill() throws Exception { assertTrue(response.contains("\"index\":3")); } + @Test + public void oneExpressionDownsamplingMissingTimestampNoFill() throws Exception { + threeSameEGaps(); + final Downsampler downsampler = Downsampler.Builder() + .setAggregator("sum") + .setInterval("1m") + .build(); + time = Timespan.Builder().setStart("1431561600") + .setAggregator("sum") + .setDownsampler(downsampler).build(); + String json = JSON.serializeToString(getDefaultQueryBuilder()); + final QueryRpc rpc = new QueryRpc(); + final HttpQuery query = NettyMocks.postQuery(tsdb, + "/api/query/exp", json); + query.getQueryBaseRoute(); // to the correct serializer + NettyMocks.mockChannelFuture(query); + + rpc.execute(tsdb, query); + final String response = + query.response().getContent().toString(Charset.forName("UTF-8")); + assertTrue(response.contains("\"alias\":\"A plus B\"")); + assertTrue(response.contains("\"dps\":[[1431561600000,1.0,4.0,0.0]")); + assertTrue(response.contains("[1431561660000,0.0,20.0,8.0]")); + assertTrue(response.contains("[1431561720000,16.0,0.0,28.0]")); + assertTrue(response.contains("\"firstTimestamp\":1431561600000")); + assertTrue(response.contains("\"index\":1")); + assertTrue(response.contains("\"metrics\":[\"A\",\"B\"]")); + assertTrue(response.contains("\"index\":2")); + assertTrue(response.contains("\"index\":3")); + } + +// @Test +// public void oneExpressionDownsamplingMissingTimestampZeroFill() throws Exception { +// threeSameEGaps(); +// final Downsampler downsampler = Downsampler.Builder() +// .setAggregator("sum") +// .setInterval("1m") +// .setFillPolicy(new NumericFillPolicy(FillPolicy.ZERO)) +// .build(); +// time = Timespan.Builder().setStart("1431561540") +// .setEnd("1431561780") +// .setAggregator("sum") +// .setDownsampler(downsampler).build(); +// String json = JSON.serializeToString(getDefaultQueryBuilder()); +// final QueryRpc rpc = new QueryRpc(); +// final HttpQuery query = NettyMocks.postQuery(tsdb, +// "/api/query/exp", json); +// query.getQueryBaseRoute(); // to the correct serializer +// NettyMocks.mockChannelFuture(query); +// +// rpc.execute(tsdb, query); +// final String response = +// query.response().getContent().toString(Charset.forName("UTF-8")); +// assertTrue(response.contains("\"alias\":\"A plus B\"")); +// assertTrue(response.contains("\"dps\":[[1431561540000,0.0,0.0,0.0]")); +// assertTrue(response.contains("[1431561600000,1.0,4.0,0.0]")); +// assertTrue(response.contains("[1431561660000,0.0,20.0,8.0]")); +// assertTrue(response.contains("[1431561720000,16.0,0.0,28.0]")); +// assertTrue(response.contains("[1431561780000,0.0,0.0,0.0]")); +// assertTrue(response.contains("\"firstTimestamp\":1431561540000")); +// assertTrue(response.contains("\"index\":1")); +// assertTrue(response.contains("\"metrics\":[\"A\",\"B\"]")); +// assertTrue(response.contains("\"index\":2")); +// assertTrue(response.contains("\"index\":3")); +// } + + @Test + public void oneExpressionNoFilter() throws Exception { + oneExtraSameE(); + final Metric metric1 = Metric.Builder().setMetric("A").setId("a") + .build(); + final Metric metric2 = Metric.Builder().setMetric("B").setId("b") + .build(); + metrics = Arrays.asList(metric1, metric2); + + final String json = JSON.serializeToString(getDefaultQueryBuilder().build()); + final QueryRpc rpc = new QueryRpc(); + final HttpQuery query = NettyMocks.postQuery(tsdb, + "/api/query/exp", json); + NettyMocks.mockChannelFuture(query); + + rpc.execute(tsdb, query); + final String response = + query.response().getContent().toString(Charset.forName("UTF-8")); +System.out.println(response); + assertTrue(response.contains("\"alias\":\"A plus B\"")); + assertTrue(response.contains("\"dps\":[[1431561600000,47.0]")); + assertTrue(response.contains("[1431561660000,52.0]")); + assertTrue(response.contains("[1431561720000,57.0]")); + assertTrue(response.contains("\"firstTimestamp\":1431561600000")); + assertTrue(response.contains("\"index\":1")); + assertTrue(response.contains("\"metrics\":[\"A\",\"B\"]")); + assertTrue(response.contains("\"index\":1")); + } + @Test public void twoExpressionsDefaultOutput() throws Exception { oneExtraSameE(); @@ -600,7 +698,7 @@ public void noIntersectionsFoundOneMetricEmpty() throws Exception { assertTrue(response.contains("\"message\":\"No intersections found")); } - @Test + @Test (expected = IllegalArgumentException.class) public void notEnoughMetrics() throws Exception { oneExtraSameE(); expressions = Arrays.asList( @@ -613,10 +711,6 @@ public void notEnoughMetrics() throws Exception { NettyMocks.mockChannelFuture(query); rpc.execute(tsdb, query); - final String response = - query.response().getContent().toString(Charset.forName("UTF-8")); - assertTrue(response.contains("\"code\":400")); - assertTrue(response.contains("\"message\":\"Not enough query results")); } protected Query.Builder getDefaultQueryBuilder() { From ca9bf6ccc5977b6252fd0856df5b5b5423d90ae2 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sun, 18 Sep 2016 15:10:29 -0700 Subject: [PATCH 214/233] Fix #708 where the sorting order of filters would change in some browsers can cause an infinite loop of loading. Now we sort after parsing and before calling the API so it's determinant. Signed-off-by: Chris Larsen --- src/tsd/client/MetricForm.java | 106 ++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 43 deletions(-) diff --git a/src/tsd/client/MetricForm.java b/src/tsd/client/MetricForm.java index fd51faeef2..0c2665416f 100644 --- a/src/tsd/client/MetricForm.java +++ b/src/tsd/client/MetricForm.java @@ -14,7 +14,9 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; + import com.google.gwt.event.dom.client.BlurEvent; import com.google.gwt.event.dom.client.BlurHandler; import com.google.gwt.event.dom.client.ClickEvent; @@ -160,6 +162,9 @@ private String parseWithMetric(final String metric) { filters.add(f); i++; } + if (!filters.isEmpty()) { + Collections.sort(filters); + } i = 0; for (int x = filters.size() - 1; x >= 0; x--) { @@ -418,53 +423,32 @@ public boolean buildQueryString(final StringBuilder url) { } } url.append(':').append(metric); - boolean non_groupbys = false; - int groupby_tags = 0; - { - final int ntags = getNumTags(); + List filters = getFilters(true); + if (!filters.isEmpty()) { url.append('{'); - for (int tag = 0; tag < ntags; tag++) { - final String tagname = getTagName(tag); - final String tagvalue = getTagValue(tag); - if (tagname.isEmpty() || tagvalue.isEmpty() || !isTagGroupby(tag)) { - if (!isTagGroupby(tag)) { - non_groupbys = true; - } - continue; + for (int i = 0; i < filters.size(); i++) { + if (i > 0) { + url.append(","); } - url.append(tagname).append('=').append(tagvalue) - .append(','); - ++groupby_tags; - } - final int last = url.length() - 1; - if (url.charAt(last) == '{') { // There was no tag. - url.setLength(last); // So remove the `{'. - } else { // Need to replace the last `,' with a `}'. - url.setCharAt(url.length() - 1, '}'); + url.append(filters.get(i).tagk) + .append("=") + .append(filters.get(i).tagv); } + url.append('}'); } - if (non_groupbys) { - if (groupby_tags == 0) { - // need this to shift group by to non-group by - url.append("{}"); - } - final int ntags = getNumTags(); + // now the non-group bys + filters = getFilters(false); + if (!filters.isEmpty()) { url.append('{'); - for (int tag = 0; tag < ntags; tag++) { - final String tagname = getTagName(tag); - final String tagvalue = getTagValue(tag); - if (tagname.isEmpty() || tagvalue.isEmpty() || isTagGroupby(tag)) { - continue; + for (int i = 0; i < filters.size(); i++) { + if (i > 0) { + url.append(","); } - url.append(tagname).append('=').append(tagvalue) - .append(','); - } - final int last = url.length() - 1; - if (url.charAt(last) == '{') { // There was no tag. - url.setLength(last); // So remove the `{'. - } else { // Need to replace the last `,' with a `}'. - url.setCharAt(url.length() - 1, '}'); + url.append(filters.get(i).tagk) + .append("=") + .append(filters.get(i).tagv); } + url.append('}'); } url.append("&o="); if (x1y2.getValue()) { @@ -472,7 +456,34 @@ public boolean buildQueryString(final StringBuilder url) { } return true; } - + + /** + * Helper method to extract the tags from the row set and sort them before + * sending to the API so that we avoid a bug wherein the sort order changes + * on reload. + * @param group_by Whether or not to fetch group by or non-group by filters. + * @return A non-null list of filters. May be empty. + */ + private List getFilters(final boolean group_by) { + final int ntags = getNumTags(); + final List filters = new ArrayList(ntags); + for (int tag = 0; tag < ntags; tag++) { + final Filter filter = new Filter(); + filter.tagk = getTagName(tag); + filter.tagv = getTagValue(tag); + filter.is_groupby = isTagGroupby(tag); + if (filter.tagk.isEmpty() || filter.tagv.isEmpty()) { + continue; + } + if (filter.is_groupby = group_by) { + filters.add(filter); + } + } + // sort on the tagk + Collections.sort(filters); + return filters; + } + private int getNumTags() { return tagtable.getRowCount() - 1; } @@ -527,7 +538,7 @@ private void addTag(final String default_tagname, final String default_value, final boolean is_groupby) { final int row = tagtable.getRowCount(); - + final ValidatedTextBox tagname = new ValidatedTextBox(); final SuggestBox suggesttagk = RemoteOracle.newSuggestBox("tagk", tagname); final ValidatedTextBox tagvalue = new ValidatedTextBox(); @@ -731,10 +742,19 @@ static final public LocalRateOptions parseRateOptions(boolean rate, String spec) } } - private static class Filter { + private static class Filter implements Comparable { String tagk; String tagv; boolean is_groupby; + + @Override + public int compareTo(final Filter filter) { + if (filter == this) { + return 0; + } + return tagk.compareTo(filter.tagk) + + tagv.compareTo(filter.tagv); + } } // ------------------- // From 6bba88cb48a7e45b61a438c1e6df8d5ffa9d5b93 Mon Sep 17 00:00:00 2001 From: Jonathan Creasy Date: Mon, 19 Sep 2016 18:08:46 -0500 Subject: [PATCH 215/233] This is the fix for #793 and 3781 Fixes #781 Fixes #793 --- src/tsd/HttpQuery.java | 49 ------------------------------------------ 1 file changed, 49 deletions(-) diff --git a/src/tsd/HttpQuery.java b/src/tsd/HttpQuery.java index c2848ee7aa..d56d0fe74e 100644 --- a/src/tsd/HttpQuery.java +++ b/src/tsd/HttpQuery.java @@ -372,8 +372,6 @@ public void internalError(final Exception cause) { HttpQuery.escapeJson(pretty_exc, buf); buf.append("\"}"); sendReply(HttpResponseStatus.INTERNAL_SERVER_ERROR, buf); - } else if (hasQueryStringParam("png")) { - sendAsPNG(HttpResponseStatus.INTERNAL_SERVER_ERROR, pretty_exc, 30); } else { sendReply(HttpResponseStatus.INTERNAL_SERVER_ERROR, makePage("Internal Server Error", "Houston, we have a problem", @@ -421,8 +419,6 @@ public void badRequest(final BadRequestException exception) { HttpQuery.escapeJson(exception.getMessage(), buf); buf.append("\"}"); sendReply(HttpResponseStatus.BAD_REQUEST, buf); - } else if (hasQueryStringParam("png")) { - sendAsPNG(HttpResponseStatus.BAD_REQUEST, exception.getMessage(), 3600); } else { sendReply(HttpResponseStatus.BAD_REQUEST, makePage("Bad Request", "Looks like it's your fault this time", @@ -456,8 +452,6 @@ public void notFound() { if (hasQueryStringParam("json")) { sendReply(HttpResponseStatus.NOT_FOUND, new StringBuilder("{\"err\":\"Page Not Found\"}")); - } else if (hasQueryStringParam("png")) { - sendAsPNG(HttpResponseStatus.NOT_FOUND, "Page Not Found", 3600); } else { sendReply(HttpResponseStatus.NOT_FOUND, PAGE_NOT_FOUND); } @@ -604,49 +598,6 @@ public void sendReply(final HttpResponseStatus status, sendBuffer(status, buf); } - /** - * Sends the given message as a PNG image. - * This method will block while image is being generated. - * It's only recommended for cases where we want to report an error back to - * the user and the user's browser expects a PNG image. Don't abuse it. - * @param status The status of the request (e.g. 200 OK or 404 Not Found). - * @param msg The message to send as an image. - * @param max_age The expiration time of this entity, in seconds. This is - * not a timestamp, it's how old the resource is allowed to be in the client - * cache. See RFC 2616 section 14.9 for more information. Use 0 to disable - * caching. - */ - public void sendAsPNG(final HttpResponseStatus status, - final String msg, - final int max_age) { - try { - final long now = System.currentTimeMillis() / 1000; - Plot plot = new Plot(now - 1, now); - HashMap params = new HashMap(1); - StringBuilder buf = new StringBuilder(1 + msg.length() + 18); - - buf.append('"'); - escapeJson(msg, buf); - buf.append("\" at graph 0.02,0.97"); - params.put("label", buf.toString()); - buf = null; - plot.setParams(params); - params = null; - final String basepath = - tsdb.getConfig().getDirectoryName("tsd.http.cachedir") - + Integer.toHexString(msg.hashCode()); - GraphHandler.runGnuplot(this, basepath, plot); - plot = null; - sendFile(status, basepath + ".png", max_age); - } catch (Exception e) { - getQueryString().remove("png"); // Avoid recursion. - this.sendReply(HttpResponseStatus.INTERNAL_SERVER_ERROR, - serializer.formatErrorV1(new RuntimeException( - "Failed to generate a PNG with the" - + " following message: " + msg, e))); - } - } - /** * Send a file (with zero-copy) to the client with a 200 OK status. * This method doesn't provide any security guarantee. The caller is From 910b433b351575e54d90185bc6613049292cf6d0 Mon Sep 17 00:00:00 2001 From: Jonathan Creasy Date: Mon, 19 Sep 2016 23:11:44 -0500 Subject: [PATCH 216/233] RPCManager, plugin loader refactor (#870) * Refactored RPCManager, allowed for disabling the built-in UI or API via configuration options Fix #830 * Pass mode in for tests * Added Logging to identify if UI and API are enabled * Added CLI Options to disable UI or API RPC endpoints * Missed a few lines with the new option --- Makefile.am | 4 +- src/opentsdb.conf | 8 + src/tools/CliOptions.java | 4 + src/tools/TSDMain.java | 4 + src/tsd/DropCachesRpc.java | 88 ++++++++++ src/tsd/RpcManager.java | 313 +++++++++++++++-------------------- src/tsd/RpcUtil.java | 37 +++++ src/utils/Config.java | 2 + test/tsd/TestRpcManager.java | 29 +++- 9 files changed, 303 insertions(+), 186 deletions(-) create mode 100644 src/tsd/DropCachesRpc.java create mode 100644 src/tsd/RpcUtil.java diff --git a/Makefile.am b/Makefile.am index d2ad17539f..73ba335146 100644 --- a/Makefile.am +++ b/Makefile.am @@ -98,7 +98,7 @@ tsdb_SRC := \ src/query/expression/PostAggregatedDataPoints.java \ src/query/expression/Scale.java \ src/query/expression/SumSeries.java \ - src/query/expression/TimeShift.java \ + src/query/expression/TimeShift.java \ src/query/expression/TimeSyncedIterator.java \ src/query/expression/UnionIterator.java \ src/query/expression/VariableIterator.java \ @@ -147,6 +147,7 @@ tsdb_SRC := \ src/tsd/AnnotationRpc.java \ src/tsd/BadRequestException.java \ src/tsd/ConnectionManager.java \ + src/tsd/DropCachesRpc.java \ src/tsd/GnuplotException.java \ src/tsd/GraphHandler.java \ src/tsd/HttpJsonSerializer.java \ @@ -164,6 +165,7 @@ tsdb_SRC := \ src/tsd/RpcHandler.java \ src/tsd/RpcPlugin.java \ src/tsd/RpcManager.java \ + src/tsd/RpcUtil.java \ src/tsd/RTPublisher.java \ src/tsd/SearchRpc.java \ src/tsd/StaticFileRpc.java \ diff --git a/src/opentsdb.conf b/src/opentsdb.conf index ba977a7b41..8ba7028a52 100644 --- a/src/opentsdb.conf +++ b/src/opentsdb.conf @@ -37,6 +37,14 @@ tsd.http.cachedir = # is False #tsd.core.auto_create_metrics = false +# Whether or not to enable the built-in UI Rpc Plugins, default +# is True +#tsd.core.enable_ui = true + +# Whether or not to enable the built-in API Rpc Plugins, default +# is True +#tsd.core.enable_api = true + # --------- STORAGE ---------- # Whether or not to enable data compaction in HBase, default is True #tsd.storage.enable_compaction = true diff --git a/src/tools/CliOptions.java b/src/tools/CliOptions.java index cf5d58b4e0..a87c45a19f 100644 --- a/src/tools/CliOptions.java +++ b/src/tools/CliOptions.java @@ -120,6 +120,10 @@ static void overloadConfig(final ArgP argp, final Config config) { // map the overrides if (entry.getKey().toLowerCase().equals("--auto-metric")) { config.overrideConfig("tsd.core.auto_create_metrics", "true"); + } else if (entry.getKey().toLowerCase().equals("--disable-ui")) { + config.overrideConfig("tsd.core.enable_ui", "false"); + } else if (entry.getKey().toLowerCase().equals("--disable-api")) { + config.overrideConfig("tsd.core.enable_api", "false"); } else if (entry.getKey().toLowerCase().equals("--table")) { config.overrideConfig("tsd.storage.hbase.data_table", entry.getValue()); } else if (entry.getKey().toLowerCase().equals("--uidtable")) { diff --git a/src/tools/TSDMain.java b/src/tools/TSDMain.java index f26a895419..a5bda860a0 100644 --- a/src/tools/TSDMain.java +++ b/src/tools/TSDMain.java @@ -93,6 +93,10 @@ public static void main(String[] args) throws IOException { "Use async NIO (default true) or traditional blocking io"); argp.addOption("--read-only", "true|false", "Set tsd.mode to ro (default false)"); + argp.addOption("--disable-ui", "true|false", + "Set tsd.core.enable_ui to false (default true)"); + argp.addOption("--disable-api", "true|false", + "Set tsd.core.enable_api to false (default true)"); argp.addOption("--backlog", "NUM", "Size of connection attempt queue (default: 3072 or kernel" + " somaxconn."); diff --git a/src/tsd/DropCachesRpc.java b/src/tsd/DropCachesRpc.java new file mode 100644 index 0000000000..9cd59d15e9 --- /dev/null +++ b/src/tsd/DropCachesRpc.java @@ -0,0 +1,88 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2013 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.tsd; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.URI; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Pattern; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.base.Splitter; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.Atomics; +import com.stumbleupon.async.Callback; +import com.stumbleupon.async.Deferred; + +import org.jboss.netty.channel.Channel; +import org.jboss.netty.handler.codec.http.HttpMethod; +import org.jboss.netty.handler.codec.http.HttpResponseStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.opentsdb.tools.BuildData; +import net.opentsdb.core.Aggregators; +import net.opentsdb.core.TSDB; +import net.opentsdb.query.filter.TagVFilter; +import net.opentsdb.stats.StatsCollector; +import net.opentsdb.utils.Config; +import net.opentsdb.utils.JSON; +import net.opentsdb.utils.PluginLoader; + +import java.io.IOException; + +/** The "dropcaches" command. */ +public final class DropCachesRpc implements TelnetRpc, HttpRpc { + private static final Logger LOG = LoggerFactory.getLogger(DropCachesRpc.class); + + public Deferred execute(final TSDB tsdb, final Channel chan, + final String[] cmd) { + dropCaches(tsdb, chan); + chan.write("Caches dropped.\n"); + return Deferred.fromResult(null); + } + + public void execute(final TSDB tsdb, final HttpQuery query) + throws IOException { + + // only accept GET/DELETE + RpcUtil.allowedMethods(query.method(), HttpMethod.GET.getName(), HttpMethod.DELETE.getName()); + + dropCaches(tsdb, query.channel()); + + if (query.apiVersion() > 0) { + final HashMap response = new HashMap(); + response.put("status", "200"); + response.put("message", "Caches dropped"); + query.sendReply(query.serializer().formatDropCachesV1(response)); + } else { // deprecated API + query.sendReply("Caches dropped.\n"); + } + } + + /** Drops in memory caches. */ + private void dropCaches(final TSDB tsdb, final Channel chan) { + LOG.warn(chan + " Dropping all in-memory caches."); + tsdb.dropCaches(); + } +} \ No newline at end of file diff --git a/src/tsd/RpcManager.java b/src/tsd/RpcManager.java index 1814326c34..86638c1a12 100644 --- a/src/tsd/RpcManager.java +++ b/src/tsd/RpcManager.java @@ -52,36 +52,36 @@ /** * Manager for the lifecycle of HttpRpcs, TelnetRpcs, * RpcPlugins, and HttpRpcPlugin. This is a - * singleton. Its lifecycle must be managed by the "container". If you are - * launching via {@code TSDMain} then shutdown (and non-lazy initialization) + * singleton. Its lifecycle must be managed by the "container". If you are + * launching via {@code TSDMain} then shutdown (and non-lazy initialization) * is taken care of. Outside of the use of {@code TSDMain}, you are responsible * for shutdown, at least. - * + * *

Here's an example of how to correctly handle shutdown manually: - * + * *

  * // Startup our TSDB instance...
  * TSDB tsdb_instance = ...;
- * 
+ *
  * // ... later, during shtudown ..
- * 
+ *
  * if (RpcManager.isInitialized()) {
  *   // Check that its actually been initialized.  We don't want to
  *   // create a new instance only to shutdown!
  *   RpcManager.instance(tsdb_instance).shutdown().join();
  * }
  * 
- * + * * @since 2.2 */ public final class RpcManager { private static final Logger LOG = LoggerFactory.getLogger(RpcManager.class); - + /** This is base path where {@link HttpRpcPlugin}s are rooted. It's used * to match incoming requests. */ @VisibleForTesting protected static final String PLUGIN_BASE_WEBPATH = "plugin"; - + /** Splitter for web paths. Removes empty strings to handle trailing or * leading slashes. For instance, all of /plugin/mytest, * plugin/mytest/, and plugin/mytest will be @@ -89,13 +89,13 @@ public final class RpcManager { private static final Splitter WEBPATH_SPLITTER = Splitter.on('/') .trimResults() .omitEmptyStrings(); - + /** Matches paths declared by {@link HttpRpcPlugin}s that are rooted in * the system's plugins path. */ private static final Pattern HAS_PLUGIN_BASE_WEBPATH = Pattern.compile( - "^/?" + PLUGIN_BASE_WEBPATH + "/?.*", + "^/?" + PLUGIN_BASE_WEBPATH + "/?.*", Pattern.CASE_INSENSITIVE | Pattern.DOTALL); - + /** Reference to our singleton instance. Set in {@link #initialize}. */ private static final AtomicReference INSTANCE = Atomics.newReference(); @@ -107,10 +107,10 @@ public final class RpcManager { private ImmutableMap http_plugin_commands; /** List of activated RPC plugins */ private ImmutableList rpc_plugins; - + /** The TSDB that owns us. */ private TSDB tsdb; - + /** * Constructor used by singleton factory method. * @param tsdb the owning TSDB instance. @@ -118,7 +118,7 @@ public final class RpcManager { private RpcManager(final TSDB tsdb) { this.tsdb = tsdb; } - + /** * Get or create the singleton instance of the manager, loading all the * plugins enabled in the given TSDB's {@link Config}. @@ -133,9 +133,9 @@ public static synchronized RpcManager instance(final TSDB tsdb) { final RpcManager manager = new RpcManager(tsdb); final String mode = Strings.nullToEmpty(tsdb.getConfig().getString("tsd.mode")); - + // Load any plugins that are enabled via Config. Fail if any plugin cannot be loaded. - + final ImmutableList.Builder rpcBuilder = ImmutableList.builder(); if (tsdb.getConfig().hasProperty("tsd.rpc.plugins")) { final String[] plugins = tsdb.getConfig().getString("tsd.rpc.plugins").split(","); @@ -155,13 +155,13 @@ public static synchronized RpcManager instance(final TSDB tsdb) { manager.initializeHttpRpcPlugins(mode, plugins, httpPluginsBuilder); } manager.http_plugin_commands = httpPluginsBuilder.build(); - + INSTANCE.set(manager); return manager; } - + /** - * @return {@code true} if the shared instance has been initialized; + * @return {@code true} if the shared instance has been initialized; * {@code false} otherwise. */ public static synchronized boolean isInitialized() { @@ -169,17 +169,17 @@ public static synchronized boolean isInitialized() { } /** - * @return list of loaded {@link RpcPlugin}s. Possibly empty but + * @return list of loaded {@link RpcPlugin}s. Possibly empty but * never {@code null}. */ @VisibleForTesting protected ImmutableList getRpcPlugins() { return rpc_plugins; } - + /** * Lookup a {@link TelnetRpc} based on given command name. Note that this - * lookup is case sensitive in that the {@code command} passed in must + * lookup is case sensitive in that the {@code command} passed in must * match a registered RPC command exactly. * @param command a telnet API command name. * @return the {@link TelnetRpc} for the given {@code command} or {@code null} @@ -188,36 +188,36 @@ protected ImmutableList getRpcPlugins() { TelnetRpc lookupTelnetRpc(final String command) { return telnet_commands.get(command); } - + /** * Lookup a built-in {@link HttpRpc} based on the given {@code queryBaseRoute}. * The lookup is based on exact match of the input parameter and the registered * {@link HttpRpc}s. - * @param queryBaseRoute the HTTP query's base route, with no trailing or + * @param queryBaseRoute the HTTP query's base route, with no trailing or * leading slashes. For example: {@code api/query} - * @return the {@link HttpRpc} for the given {@code queryBaseRoute} or + * @return the {@link HttpRpc} for the given {@code queryBaseRoute} or * {@code null} if not found. */ HttpRpc lookupHttpRpc(final String queryBaseRoute) { return http_commands.get(queryBaseRoute); } - + /** - * Lookup a user-supplied {@link HttpRpcPlugin} for the given - * {@code queryBaseRoute}. The lookup is based on exact match of the input + * Lookup a user-supplied {@link HttpRpcPlugin} for the given + * {@code queryBaseRoute}. The lookup is based on exact match of the input * parameter and the registered {@link HttpRpcPlugin}s. * @param queryBaseRoute the value of {@link HttpRpcPlugin#getPath()} with no * trailing or leading slashes. - * @return the {@link HttpRpcPlugin} for the given {@code queryBaseRoute} or + * @return the {@link HttpRpcPlugin} for the given {@code queryBaseRoute} or * {@code null} if not found. */ HttpRpcPlugin lookupHttpRpcPlugin(final String queryBaseRoute) { return http_plugin_commands.get(queryBaseRoute); } - + /** * @param uri HTTP request URI, with or without query parameters. - * @return {@code true} if the URI represents a request for a + * @return {@code true} if the URI represents a request for a * {@link HttpRpcPlugin}; {@code false} otherwise. Note that this * method returning true says nothing about * whether or not there is a {@link HttpRpcPlugin} registered @@ -233,12 +233,12 @@ boolean isHttpRpcPluginPath(final String uri) { if (qmark != -1) { path = uri.substring(0, qmark); } - + final List parts = WEBPATH_SPLITTER.splitToList(path); return (parts.size() > 1 && parts.get(0).equals(PLUGIN_BASE_WEBPATH)); } } - + /** * Load and init instances of {@link TelnetRpc}s and {@link HttpRpc}s. * These are not generally configurable via TSDB config. @@ -248,67 +248,75 @@ boolean isHttpRpcPluginPath(final String uri) { * instances. * @param http a map of API endpoints to {@link HttpRpc} instances. */ - private void initializeBuiltinRpcs(final String mode, + private void initializeBuiltinRpcs(final String mode, final ImmutableMap.Builder telnet, final ImmutableMap.Builder http) { + + final Boolean enableApi = tsdb.getConfig().getString("tsd.core.enable_api").equals("true"); + final Boolean enableUi = tsdb.getConfig().getString("tsd.core.enable_ui").equals("true"); + final Boolean enableDieDieDie = tsdb.getConfig().getString("tsd.no_diediedie").equals("false"); + + LOG.info("Mode: {}, HTTP UI Enabled: {}, HTTP API Enabled: {}", mode, enableUi, enableApi); + if (mode.equals("rw") || mode.equals("wo")) { final PutDataPointRpc put = new PutDataPointRpc(); telnet.put("put", put); - http.put("api/put", put); + if (enableApi) { + http.put("api/put", put); + } } - + if (mode.equals("rw") || mode.equals("ro")) { - http.put("", new HomePage()); final StaticFileRpc staticfile = new StaticFileRpc(); - http.put("favicon.ico", staticfile); - http.put("s", staticfile); - final StatsRpc stats = new StatsRpc(); - telnet.put("stats", stats); - http.put("stats", stats); - http.put("api/stats", stats); + final DropCachesRpc dropcaches = new DropCachesRpc(); + final ListAggregators aggregators = new ListAggregators(); + final SuggestRpc suggest_rpc = new SuggestRpc(); + final AnnotationRpc annotation_rpc = new AnnotationRpc(); + final Version version = new Version(); - final DropCaches dropcaches = new DropCaches(); + telnet.put("stats", stats); telnet.put("dropcaches", dropcaches); - http.put("dropcaches", dropcaches); - http.put("api/dropcaches", dropcaches); + telnet.put("version", version); + telnet.put("exit", new Exit()); + telnet.put("help", new Help()); - final ListAggregators aggregators = new ListAggregators(); - http.put("aggregators", aggregators); - http.put("api/aggregators", aggregators); + if (enableUi) { + http.put("", new HomePage()); + http.put("aggregators", aggregators); + http.put("dropcaches", dropcaches); + http.put("favicon.ico", staticfile); + http.put("logs", new LogsRpc()); + http.put("q", new GraphHandler()); + http.put("s", staticfile); + http.put("stats", stats); + http.put("suggest", suggest_rpc); + http.put("version", version); + } - final SuggestRpc suggest_rpc = new SuggestRpc(); - http.put("suggest", suggest_rpc); - http.put("api/suggest", suggest_rpc); - - http.put("logs", new LogsRpc()); - http.put("q", new GraphHandler()); - http.put("api/serializers", new Serializers()); - http.put("api/uid", new UniqueIdRpc()); - http.put("api/query", new QueryRpc()); - http.put("api/tree", new TreeRpc()); - { - final AnnotationRpc annotation_rpc = new AnnotationRpc(); + if (enableApi) { + http.put("api/aggregators", aggregators); http.put("api/annotation", annotation_rpc); http.put("api/annotations", annotation_rpc); - } - http.put("api/search", new SearchRpc()); - http.put("api/config", new ShowConfig()); - - if (tsdb.getConfig().getString("tsd.no_diediedie").equals("false")) { - final DieDieDie diediedie = new DieDieDie(); - telnet.put("diediedie", diediedie); - http.put("diediedie", diediedie); - } - { - final Version version = new Version(); - telnet.put("version", version); - http.put("version", version); + http.put("api/config", new ShowConfig()); + http.put("api/dropcaches", dropcaches); + http.put("api/query", new QueryRpc()); + http.put("api/search", new SearchRpc()); + http.put("api/serializers", new Serializers()); + http.put("api/stats", stats); + http.put("api/suggest", suggest_rpc); + http.put("api/tree", new TreeRpc()); + http.put("api/uid", new UniqueIdRpc()); http.put("api/version", version); } + } - telnet.put("exit", new Exit()); - telnet.put("help", new Help()); + if (enableDieDieDie) { + final DieDieDie diediedie = new DieDieDie(); + telnet.put("diediedie", diediedie); + if (enableUi) { + http.put("diediedie", diediedie); + } } } @@ -317,10 +325,10 @@ private void initializeBuiltinRpcs(final String mode, * {@code pluginClassNames}. * @param mode is this TSD in read/write ("rw") or read-only ("ro") * mode? - * @param pluginClassNames fully-qualified class names that are + * @param pluginClassNames fully-qualified class names that are * instances of {@link HttpRpcPlugin}s - * @param http a map of canonicalized paths - * (obtained via {@link #canonicalizePluginPath(String)}) + * @param http a map of canonicalized paths + * (obtained via {@link #canonicalizePluginPath(String)}) * to {@link HttpRpcPlugin} instance. */ @VisibleForTesting @@ -338,7 +346,7 @@ protected void initializeHttpRpcPlugins(final String mode, } /** - * Ensure that the given path for an {@link HttpRpcPlugin} is valid. This + * Ensure that the given path for an {@link HttpRpcPlugin} is valid. This * method simply returns for valid inputs; throws and exception otherwise. * @param path a request path, no query parameters, etc. * @throws IllegalArgumentException on invalid paths. @@ -349,9 +357,9 @@ protected void validateHttpRpcPluginPath(final String path) { "Invalid HttpRpcPlugin path. Path is null or empty."); final String testPath = path.trim(); Preconditions.checkArgument(!HAS_PLUGIN_BASE_WEBPATH.matcher(path).matches(), - "Invalid HttpRpcPlugin path %s. Path contains system's plugin base path.", + "Invalid HttpRpcPlugin path %s. Path contains system's plugin base path.", testPath); - + URI uri = URI.create(testPath); Preconditions.checkArgument(!Strings.isNullOrEmpty(uri.getPath()), "Invalid HttpRpcPlugin path %s. Parsed path is null or empty.", testPath); @@ -384,18 +392,18 @@ protected String canonicalizePluginPath(final String origPath) { /** * Load and init the {@link RpcPlugin}s provided as an array of * {@code pluginClassNames}. - * @param pluginClassNames fully-qualified class names that are + * @param pluginClassNames fully-qualified class names that are * instances of {@link RpcPlugin}s * @param rpcs a list of loaded and initialized plugins */ - private void initializeRpcPlugins(final String[] pluginClassNames, + private void initializeRpcPlugins(final String[] pluginClassNames, final ImmutableList.Builder rpcs) { for (final String plugin : pluginClassNames) { final RpcPlugin rpc = createAndInitialize(plugin, RpcPlugin.class); rpcs.add(rpc); } } - + /** * Helper method to load and initialize a given plugin class. This uses reflection * because plugins share no common interfaces. (They could though!) @@ -406,7 +414,7 @@ private void initializeRpcPlugins(final String[] pluginClassNames, @VisibleForTesting protected T createAndInitialize(final String pluginClassName, final Class pluginClass) { final T instance = PluginLoader.loadSpecificPlugin(pluginClassName, pluginClass); - Preconditions.checkState(instance != null, + Preconditions.checkState(instance != null, "Unable to locate %s using name '%s", pluginClass, pluginClassName); try { final Method initMeth = instance.getClass().getMethod("initialize", TSDB.class); @@ -421,9 +429,9 @@ protected T createAndInitialize(final String pluginClassName, final Class throw new RuntimeException("Failed to initialize " + instance.getClass(), e); } } - + /** - * Called to gracefully shutdown the plugin. Implementations should close + * Called to gracefully shutdown the plugin. Implementations should close * any IO they have open * @return A deferred object that indicates the completion of the request. * The {@link Object} has not special meaning and can be {@code null} @@ -434,22 +442,22 @@ public Deferred> shutdown() { INSTANCE.set(null); final Collection> deferreds = Lists.newArrayList(); - + if (http_plugin_commands != null) { for (final Map.Entry entry : http_plugin_commands.entrySet()) { deferreds.add(entry.getValue().shutdown()); } } - + if (rpc_plugins != null) { for (final RpcPlugin rpc : rpc_plugins) { deferreds.add(rpc.shutdown()); } } - + return Deferred.groupInOrder(deferreds); } - + /** * Collect stats on the shared instance of {@link RpcManager}. */ @@ -470,7 +478,7 @@ static void collectStats(final StatsCollector collector) { if (manager.http_plugin_commands != null) { try { collector.addExtraTag("plugin", "httprpc"); - for (final Map.Entry entry + for (final Map.Entry entry : manager.http_plugin_commands.entrySet()) { entry.getValue().collectStats(collector); } @@ -480,7 +488,7 @@ static void collectStats(final StatsCollector collector) { } } } - + // ---------------------------- // // Individual command handlers. // // ---------------------------- // @@ -557,7 +565,7 @@ public Deferred execute(final TSDB tsdb, final Channel chan, /** The home page ("GET /"). */ private static final class HomePage implements HttpRpc { - public void execute(final TSDB tsdb, final HttpQuery query) + public void execute(final TSDB tsdb, final HttpQuery query) throws IOException { final StringBuilder buf = new StringBuilder(2048); buf.append("
" @@ -571,19 +579,15 @@ public void execute(final TSDB tsdb, final HttpQuery query) "OpenTSDB", "", buf.toString())); } } - + /** The "/aggregators" endpoint. */ private static final class ListAggregators implements HttpRpc { - public void execute(final TSDB tsdb, final HttpQuery query) + public void execute(final TSDB tsdb, final HttpQuery query) throws IOException { - - // only accept GET/POST - if (query.method() != HttpMethod.GET && query.method() != HttpMethod.POST) { - throw new BadRequestException(HttpResponseStatus.METHOD_NOT_ALLOWED, - "Method not allowed", "The HTTP method [" + query.method().getName() + - "] is not permitted for this endpoint"); - } - + + // only accept GET / POST + RpcUtil.allowedMethods(query.method(), HttpMethod.GET.getName(), HttpMethod.POST.getName()); + if (query.apiVersion() > 0) { query.sendReply( query.serializer().formatAggregatorsV1(Aggregators.set())); @@ -604,16 +608,12 @@ public Deferred execute(final TSDB tsdb, final Channel chan, return Deferred.fromResult(null); } - public void execute(final TSDB tsdb, final HttpQuery query) throws + public void execute(final TSDB tsdb, final HttpQuery query) throws IOException { - - // only accept GET/POST - if (query.method() != HttpMethod.GET && query.method() != HttpMethod.POST) { - throw new BadRequestException(HttpResponseStatus.METHOD_NOT_ALLOWED, - "Method not allowed", "The HTTP method [" + query.method().getName() + - "] is not permitted for this endpoint"); - } - + + // only accept GET / POST + RpcUtil.allowedMethods(query.method(), HttpMethod.GET.getName(), HttpMethod.POST.getName()); + final HashMap version = new HashMap(); version.put("version", BuildData.version); version.put("short_revision", BuildData.short_revision); @@ -628,7 +628,7 @@ public void execute(final TSDB tsdb, final HttpQuery query) throws if (query.apiVersion() > 0) { query.sendReply(query.serializer().formatVersionV1(version)); } else { - final boolean json = query.request().getUri().endsWith("json"); + final boolean json = query.request().getUri().endsWith("json"); if (json) { query.sendReply(JSON.serializeToBytes(version)); } else { @@ -643,82 +643,37 @@ public void execute(final TSDB tsdb, final HttpQuery query) throws } } } - - /** The "dropcaches" command. */ - private static final class DropCaches implements TelnetRpc, HttpRpc { - public Deferred execute(final TSDB tsdb, final Channel chan, - final String[] cmd) { - dropCaches(tsdb, chan); - chan.write("Caches dropped.\n"); - return Deferred.fromResult(null); - } - - public void execute(final TSDB tsdb, final HttpQuery query) - throws IOException { - dropCaches(tsdb, query.channel()); - - // only accept GET/POST - if (query.method() != HttpMethod.GET && query.method() != HttpMethod.POST) { - throw new BadRequestException(HttpResponseStatus.METHOD_NOT_ALLOWED, - "Method not allowed", "The HTTP method [" + query.method().getName() + - "] is not permitted for this endpoint"); - } - - if (query.apiVersion() > 0) { - final HashMap response = new HashMap(); - response.put("status", "200"); - response.put("message", "Caches dropped"); - query.sendReply(query.serializer().formatDropCachesV1(response)); - } else { // deprecated API - query.sendReply("Caches dropped.\n"); - } - } - - /** Drops in memory caches. */ - private void dropCaches(final TSDB tsdb, final Channel chan) { - LOG.warn(chan + " Dropping all in-memory caches."); - tsdb.dropCaches(); - } - } - /** The /api/formatters endpoint + /** The /api/formatters endpoint * @since 2.0 */ private static final class Serializers implements HttpRpc { - public void execute(final TSDB tsdb, final HttpQuery query) + public void execute(final TSDB tsdb, final HttpQuery query) throws IOException { - // only accept GET/POST - if (query.method() != HttpMethod.GET && query.method() != HttpMethod.POST) { - throw new BadRequestException(HttpResponseStatus.METHOD_NOT_ALLOWED, - "Method not allowed", "The HTTP method [" + query.method().getName() + - "] is not permitted for this endpoint"); - } - + // only accept GET / POST + RpcUtil.allowedMethods(query.method(), HttpMethod.GET.getName(), HttpMethod.POST.getName()); + switch (query.apiVersion()) { case 0: case 1: query.sendReply(query.serializer().formatSerializersV1()); break; - default: - throw new BadRequestException(HttpResponseStatus.NOT_IMPLEMENTED, - "Requested API version not implemented", "Version " + + default: + throw new BadRequestException(HttpResponseStatus.NOT_IMPLEMENTED, + "Requested API version not implemented", "Version " + query.apiVersion() + " is not implemented"); } } } - + private static final class ShowConfig implements HttpRpc { @Override public void execute(TSDB tsdb, HttpQuery query) throws IOException { // only accept GET/POST - if (query.method() != HttpMethod.GET && query.method() != HttpMethod.POST) { - throw new BadRequestException(HttpResponseStatus.METHOD_NOT_ALLOWED, - "Method not allowed", "The HTTP method [" + query.method().getName() + - "] is not permitted for this endpoint"); - } - + RpcUtil.allowedMethods(query.method(), HttpMethod.GET.getName(), HttpMethod.POST.getName()); + final String[] uri = query.explodeAPIPath(); final String endpoint = uri.length > 1 ? uri[1].toLowerCase() : ""; - + if (endpoint.equals("filters")) { switch (query.apiVersion()) { case 0: @@ -726,9 +681,9 @@ public void execute(TSDB tsdb, HttpQuery query) throws IOException { query.sendReply(query.serializer().formatFilterConfigV1( TagVFilter.loadedFilters())); break; - default: - throw new BadRequestException(HttpResponseStatus.NOT_IMPLEMENTED, - "Requested API version not implemented", "Version " + + default: + throw new BadRequestException(HttpResponseStatus.NOT_IMPLEMENTED, + "Requested API version not implemented", "Version " + query.apiVersion() + " is not implemented"); } } else { @@ -737,9 +692,9 @@ public void execute(TSDB tsdb, HttpQuery query) throws IOException { case 1: query.sendReply(query.serializer().formatConfigV1(tsdb.getConfig())); break; - default: - throw new BadRequestException(HttpResponseStatus.NOT_IMPLEMENTED, - "Requested API version not implemented", "Version " + + default: + throw new BadRequestException(HttpResponseStatus.NOT_IMPLEMENTED, + "Requested API version not implemented", "Version " + query.apiVersion() + " is not implemented"); } } diff --git a/src/tsd/RpcUtil.java b/src/tsd/RpcUtil.java new file mode 100644 index 0000000000..07660e208f --- /dev/null +++ b/src/tsd/RpcUtil.java @@ -0,0 +1,37 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2010-2014 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.tsd; + +import org.jboss.netty.handler.codec.http.HttpMethod; +import org.jboss.netty.handler.codec.http.HttpResponseStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +public class RpcUtil { + + private static final Logger LOG = LoggerFactory.getLogger(RpcUtil.class); + + public static void allowedMethods(HttpMethod requestMethod, String... allowedMethods) { + for(String method : allowedMethods) { + LOG.debug(String.format("Trying Method: %s", method)); + if (requestMethod.getName() == method) { + LOG.debug(String.format("Method Allowed: %s", method)); + return; + } + } + throw new BadRequestException(HttpResponseStatus.METHOD_NOT_ALLOWED, + "Method not allowed", "The HTTP method [" + requestMethod.getName() + "] is not permitted for this endpoint"); + } +} diff --git a/src/utils/Config.java b/src/utils/Config.java index 4927d43fbb..782bf36430 100644 --- a/src/utils/Config.java +++ b/src/utils/Config.java @@ -488,6 +488,8 @@ protected void setDefaults() { default_map.put("tsd.core.auto_create_tagks", "true"); default_map.put("tsd.core.auto_create_tagvs", "true"); default_map.put("tsd.core.connections.limit", "0"); + default_map.put("tsd.core.enable_api", "true"); + default_map.put("tsd.core.enable_ui", "true"); default_map.put("tsd.core.meta.enable_realtime_ts", "false"); default_map.put("tsd.core.meta.enable_realtime_uid", "false"); default_map.put("tsd.core.meta.enable_tsuid_incrementing", "false"); diff --git a/test/tsd/TestRpcManager.java b/test/tsd/TestRpcManager.java index 58a8193487..fc062bc506 100644 --- a/test/tsd/TestRpcManager.java +++ b/test/tsd/TestRpcManager.java @@ -46,6 +46,12 @@ public class TestRpcManager { @Before public void before() { Config config = mock(Config.class); + when(config.getString("tsd.core.enable_api")) + .thenReturn("true"); + when(config.getString("tsd.core.enable_ui")) + .thenReturn("true"); + when(config.getString("tsd.no_diediedie")) + .thenReturn("false"); TSDB tsdb = mock(TSDB.class); when(tsdb.getConfig()).thenReturn(config); mock_tsdb_no_plugins = tsdb; @@ -62,10 +68,16 @@ public void after() throws Exception { public void loadHttpRpcPlugins() throws Exception { Config config = mock(Config.class); when(config.hasProperty("tsd.http.rpc.plugins")) - .thenReturn(true); + .thenReturn(true); when(config.getString("tsd.http.rpc.plugins")) .thenReturn("net.opentsdb.tsd.DummyHttpRpcPlugin"); - + when(config.getString("tsd.core.enable_api")) + .thenReturn("true"); + when(config.getString("tsd.core.enable_ui")) + .thenReturn("true"); + when(config.getString("tsd.no_diediedie")) + .thenReturn("false"); + TSDB tsdb = mock(TSDB.class); when(tsdb.getConfig()).thenReturn(config); @@ -80,17 +92,22 @@ public void loadHttpRpcPlugins() throws Exception { public void loadRpcPlugin() throws Exception { Config config = mock(Config.class); when(config.hasProperty("tsd.rpc.plugins")) - .thenReturn(true); + .thenReturn(true); when(config.getString("tsd.rpc.plugins")) .thenReturn("net.opentsdb.tsd.DummyRpcPlugin"); - when(config.hasProperty("tsd.rpcplugin.DummyRPCPlugin.hosts")) - .thenReturn(true); + .thenReturn(true); when(config.getString("tsd.rpcplugin.DummyRPCPlugin.hosts")) .thenReturn("blah"); when(config.getInt("tsd.rpcplugin.DummyRPCPlugin.port")) .thenReturn(1000); - + when(config.getString("tsd.core.enable_api")) + .thenReturn("true"); + when(config.getString("tsd.core.enable_ui")) + .thenReturn("true"); + when(config.getString("tsd.no_diediedie")) + .thenReturn("false"); + TSDB tsdb = mock(TSDB.class); when(tsdb.getConfig()).thenReturn(config); From 6cb553a98142d5b88a2049f3b4a371f3957c35c2 Mon Sep 17 00:00:00 2001 From: Jonathan Creasy Date: Mon, 19 Sep 2016 23:20:55 -0500 Subject: [PATCH 217/233] Added Dockerfile and tool to build docker root (#871) --- .gitignore | 6 ++++++ tools/docker/Dockerfile | 43 +++++++++++++++++++++++++++++++++++++++++ tools/docker/docker.sh | 16 +++++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 tools/docker/Dockerfile create mode 100755 tools/docker/docker.sh diff --git a/.gitignore b/.gitignore index 41729cc410..60fb346c0f 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,9 @@ src-main src-test plugin_test.jar bin/ + +#Docker +tools/docker/libs +tools/docker/*.jar +tools/docker/logback.xml +tools/docker/opentsdb.conf diff --git a/tools/docker/Dockerfile b/tools/docker/Dockerfile new file mode 100644 index 0000000000..c9410133e1 --- /dev/null +++ b/tools/docker/Dockerfile @@ -0,0 +1,43 @@ +FROM java:openjdk-8-alpine + +MAINTAINER jonathan.creasy@gmail.com + +ENV VERSION 2.3.0-RC1 +ENV WORKDIR /usr/share/opentsdb +ENV LOGDIR /var/log/opentsdb +ENV DATADIR /data/opentsdb +ENV ETCDIR /etc/opentsdb + +RUN mkdir -p $WORKDIR/static +RUN mkdir -p $WORKDIR/libs +RUN mkdir -p $WORKDIR/third_party +RUN mkdir -p $WORKDIR/resources +RUN mkdir -p $DATADIR/cache +RUN mkdir -p $LOGDIR +RUN mkdir -p $ETCDIR + +ENV CONFIG $ETCDIR/opentsdb.conf +ENV STATICROOT $WORKDIR/static +ENV CACHEDIR $DATADIR/cache + +ENV CLASSPATH $WORKDIR:$WORKDIR/tsdb-$VERSION.jar:$WORKDIR/libs/*:$WORKDIR/logback.xml +ENV CLASS net.opentsdb.tools.TSDMain + +# It is expected these might need to be passed in with the -e flag +ENV JAVA_OPTS="-Xms512m -Xmx2048m" +ENV ZKQUORUM zookeeper:2181 +ENV ZKBASEDIR /hbase +ENV TSDB_OPTS "--read-only --disable-ui" +ENV TSDB_PORT 4244 + +WORKDIR $WORKDIR + +ADD libs $WORKDIR/libs +ADD logback.xml $WORKDIR +ADD tsdb-$VERSION.jar $WORKDIR +ADD opentsdb.conf $ETCDIR/opentsdb.conf + +VOLUME ["/etc/openstsdb"] +VOLUME ["/data/opentsdb"] + +ENTRYPOINT java -enableassertions -enablesystemassertions -classpath ${CLASSPATH} ${CLASS} --config=${CONFIG} --staticroot=${STATICROOT} --cachedir=${CACHEDIR} --port=${TSDB_PORT} --zkquorum=${ZKQUORUM} --zkbasedir=${ZKBASEDIR} ${TSDB_OPTS} diff --git a/tools/docker/docker.sh b/tools/docker/docker.sh new file mode 100755 index 0000000000..17686a0e61 --- /dev/null +++ b/tools/docker/docker.sh @@ -0,0 +1,16 @@ +#!/bin/bash -x +BUILDROOT=./build; +TOOLS=./tools +DOCKER=$BUILDROOT/docker; +rm -r $DOCKER; +mkdir -p $DOCKER; +SOURCE_PATH=$BUILDROOT; +DEST_PATH=$DOCKER/libs; +mkdir -p $DEST_PATH; +cp ${TOOLS}/docker/Dockerfile ${DOCKER}; +cp ${BUILDROOT}/../src/opentsdb.conf ${DOCKER}; +cp ${BUILDROOT}/../src/logback.xml ${DOCKER}; +#cp ${BUILDROOT}/../src/mygnuplot.sh ${DOCKER}; +cp ${SOURCE_PATH}/tsdb-2.3.0-RC1.jar ${DOCKER}; +cp ${SOURCE_PATH}/third_party/*/*.jar ${DEST_PATH}; +docker build -t opentsdb/opentsdb $DOCKER From 9e2774c916a26c6049e201df0c64a8f355489566 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Mon, 26 Sep 2016 22:12:50 -0700 Subject: [PATCH 218/233] Fix #837 by making sure the fuzzy filter is created only when fuzzy filtering is enabled and explicit tags are enabled. Also add some unit tests for the above. Signed-off-by: Chris Larsen --- src/query/QueryUtil.java | 2 +- test/query/TestQueryUtil.java | 148 ++++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 test/query/TestQueryUtil.java diff --git a/src/query/QueryUtil.java b/src/query/QueryUtil.java index 1a61c45a0a..c7b9782b2a 100644 --- a/src/query/QueryUtil.java +++ b/src/query/QueryUtil.java @@ -231,7 +231,7 @@ public static void setDataTableScanFilter( byteRegexToString(regex)); } - if (!explicit_tags || !enable_fuzzy_filter) { + if (!(explicit_tags && enable_fuzzy_filter)) { scanner.setFilter(regex_filter); return; } diff --git a/test/query/TestQueryUtil.java b/test/query/TestQueryUtil.java new file mode 100644 index 0000000000..03760e8499 --- /dev/null +++ b/test/query/TestQueryUtil.java @@ -0,0 +1,148 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2016 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.query; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.hbase.async.Bytes.ByteMap; +import org.hbase.async.FilterList; +import org.hbase.async.KeyRegexpFilter; +import org.hbase.async.ScanFilter; +import org.hbase.async.Scanner; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import com.google.common.collect.Lists; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({ Scanner.class }) +public class TestQueryUtil { + private Scanner scanner; + + @Before + public void before() throws Exception { + scanner = mock(Scanner.class); + } + + @Test + public void setDataTableScanFilterNoOp() throws Exception { + QueryUtil.setDataTableScanFilter( + scanner, + Lists.newArrayList(), + new ByteMap(), + false, + false, + 0); + verify(scanner, never()).getCurrentKey(); + verify(scanner, never()).setFilter(any(ScanFilter.class)); + verify(scanner, never()).setStartKey(any(byte[].class)); + verify(scanner, never()).setStopKey(any(byte[].class)); + } + + @Test + public void setDataTableScanFilterGroupBy() throws Exception { + QueryUtil.setDataTableScanFilter( + scanner, + Lists.newArrayList(new byte[] { 0, 0, 1 }), + new ByteMap(), + false, + false, + 0); + verify(scanner, never()).getCurrentKey(); + // TODO - validate the regex + verify(scanner, times(1)).setFilter(any(KeyRegexpFilter.class)); + verify(scanner, never()).setStartKey(any(byte[].class)); + verify(scanner, never()).setStopKey(any(byte[].class)); + } + + @Test + public void setDataTableScanFilterTags() throws Exception { + final ByteMap tags = new ByteMap(); + tags.put(new byte[] { 0, 0, 1 }, new byte[][] { new byte[] {0, 0, 1} }); + QueryUtil.setDataTableScanFilter( + scanner, + Lists.newArrayList(), + tags, + false, + false, + 0); + verify(scanner, never()).getCurrentKey(); + // TODO - validate the regex + verify(scanner, times(1)).setFilter(any(KeyRegexpFilter.class)); + verify(scanner, never()).setStartKey(any(byte[].class)); + verify(scanner, never()).setStopKey(any(byte[].class)); + } + + @Test + public void setDataTableScanFilterEnableFuzzy() throws Exception { + final ByteMap tags = new ByteMap(); + tags.put(new byte[] { 0, 0, 1 }, new byte[][] { new byte[] {0, 0, 1} }); + QueryUtil.setDataTableScanFilter( + scanner, + Lists.newArrayList(), + tags, + false, + true, + 0); + verify(scanner, never()).getCurrentKey(); + // TODO - validate the regex + verify(scanner, times(1)).setFilter(any(KeyRegexpFilter.class)); + verify(scanner, never()).setStartKey(any(byte[].class)); + verify(scanner, never()).setStopKey(any(byte[].class)); + } + + @Test + public void setDataTableScanFilterEnableExplicit() throws Exception { + final ByteMap tags = new ByteMap(); + tags.put(new byte[] { 0, 0, 1 }, new byte[][] { new byte[] {0, 0, 1} }); + QueryUtil.setDataTableScanFilter( + scanner, + Lists.newArrayList(), + tags, + true, + false, + 0); + verify(scanner, never()).getCurrentKey(); + // TODO - validate the regex + verify(scanner, times(1)).setFilter(any(KeyRegexpFilter.class)); + verify(scanner, never()).setStartKey(any(byte[].class)); + verify(scanner, never()).setStopKey(any(byte[].class)); + } + + @Test + public void setDataTableScanFilterEnableBoth() throws Exception { + when(scanner.getCurrentKey()).thenReturn(new byte[] { 0, 0, 0, 1 }); + final ByteMap tags = new ByteMap(); + tags.put(new byte[] { 0, 0, 1 }, new byte[][] { new byte[] {0, 0, 1} }); + QueryUtil.setDataTableScanFilter( + scanner, + Lists.newArrayList(), + tags, + true, + true, + 0); + verify(scanner, times(2)).getCurrentKey(); + // TODO - validate the regex and fuzzy filter + verify(scanner, times(1)).setFilter(any(FilterList.class)); + verify(scanner, times(1)).setStartKey(any(byte[].class)); + verify(scanner, times(1)).setStopKey(any(byte[].class)); + } +} From e7aaa5fa208f87a15aafd9d268d68e3bc718ebbf Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sat, 8 Oct 2016 10:30:55 -0700 Subject: [PATCH 219/233] Fix a directory redirection error in the makefile when building an RPM. --- Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index 73ba335146..0c010a7911 100644 --- a/Makefile.am +++ b/Makefile.am @@ -568,7 +568,7 @@ install-data-local: staticroot install-data-lib install-data-tools \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ dstdir=`dirname "$(DESTDIR)$(staticdir)/$$p"`; \ if test -d "$$dstdir"; then :; else \ - echo " $(mkdir_p) '$$dstdir'"; ../$(mkdir_p) "$$dstdir"; fi; \ + echo " $(mkdir_p) '$$dstdir'"; $(mkdir_p) "$$dstdir"; fi; \ echo " $(INSTALL_DATA) '$$d$$p' '$(DESTDIR)$(staticdir)/$$p'"; \ $(INSTALL_DATA) "$$d$$p" "$(DESTDIR)$(staticdir)/$$p"; \ done From 8129e3a9faeec72927c51ddba048b323f96f308d Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sat, 8 Oct 2016 10:35:12 -0700 Subject: [PATCH 220/233] Bump to version 2.3.0-RC2 --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index ea48f48ce8..b55a385569 100644 --- a/configure.ac +++ b/configure.ac @@ -14,7 +14,7 @@ # along with this library. If not, see . # Semantic Versioning (see http://semver.org/). -AC_INIT([opentsdb], [2.3.0-RC1], [opentsdb@googlegroups.com]) +AC_INIT([opentsdb], [2.3.0-RC2], [opentsdb@googlegroups.com]) AC_CONFIG_AUX_DIR([build-aux]) AM_INIT_AUTOMAKE([foreign]) From 5e649f2f1a80cb8446a3e5152defb24c09cf47b8 Mon Sep 17 00:00:00 2001 From: dfsklar Date: Thu, 1 Sep 2016 11:21:05 -0400 Subject: [PATCH 221/233] Update HttpQuery.java --- src/tsd/HttpQuery.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tsd/HttpQuery.java b/src/tsd/HttpQuery.java index d56d0fe74e..49c30268d6 100644 --- a/src/tsd/HttpQuery.java +++ b/src/tsd/HttpQuery.java @@ -976,7 +976,7 @@ protected Logger logger() { + "" + "

" - + "T" - + "S" - + "D" - + "   
"; + + "
" + + "" + + " 
"; private static final String PAGE_BODY_MID = "
" + "" + "
" - + "" + + "" + " 
"; From c4b467959918da1b946bf605952e24d370bcb7e0 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sat, 8 Oct 2016 12:27:03 -0700 Subject: [PATCH 222/233] Copy the full contents of the tools directory to prevent errors in building the Debian package. --- Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index 0c010a7911..ab2a3fa505 100644 --- a/Makefile.am +++ b/Makefile.am @@ -861,7 +861,7 @@ debian: dist staticroot cp -r gwt/queryui/* $(distdir)/debian/usr/share/opentsdb/static `for dep_jar in $(tsdb_DEPS); do cp $$dep_jar \ $(distdir)/debian/usr/share/opentsdb/lib; done;` - cp $(top_srcdir)/tools/* $(distdir)/debian/usr/share/opentsdb/tools + cp -r $(top_srcdir)/tools/* $(distdir)/debian/usr/share/opentsdb/tools dpkg -b $(distdir)/debian $(distdir)/opentsdb-$(PACKAGE_VERSION)_all.deb .PHONY: jar doc check gwtc gwtdev printdeps staticroot gwttsd rpm From 2ec0b33f25624331eed31c53fe3f4582cb39dab6 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sat, 8 Oct 2016 12:27:37 -0700 Subject: [PATCH 223/233] Cut release 2.3.0RC2 --- NEWS | 43 ++++++++++++++++++++++++++++++++++++++++++- THANKS | 6 ++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index d2dabe3de2..335ee67c42 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,26 @@ OpenTSDB - User visible changes. +* Version 2.3.0 RC2 (2016-10-08) + +Noteworthy Changes: + - Added a docker file and tool to build TSD docker containers (#871). + - Log X-Forwarded-For addresses when handling HTTP requests. + - Expand aggregator options in the Nagios check script. + - Allow enabling or disabling the HTTP API or UI. + - TSD will now exit when an unrecognized CLI param is passed. + +Bug Fixes: + - Improved ALPN version detection when using Google Bigtable. + - Fix the DumpSeries class to support appended data point types. + - Fix queries where groupby is set to false on all filters. + - Fix a missing attribute in the Nagios check script (#728). + - Fix a major security bug where requesting a PNG with certain URI params could execute code + on the host (#793, #781). + - Return a proper error code when dropping caches with the DELETE HTTP verb (#830). + - Fix backwards compatibility with HBase 0.94 when using explicit tags by removing the + fuzzy filter (#837). + - Fix an RPM build issue when creating the GWT directory. + * Version 2.3.0 RC1 (2016-05-02) Noteworthy Changes: @@ -31,7 +52,27 @@ Bug Fixes: - Restore the ability to create TSMeta objects via URI - Restore raw data points (along with post-filtered data points) in query stats - Built in UI will now properly display global annotations when the query string is passed - - + +Noteworthy Changes: + - Improve metadata storage performance by removing an extra getFromStorage() call. + - Add global Annotations to the gnuplot graphs (#773) + - Allow creation of a TSMeta object without a TSUID (#778) + - Move to AsyncHBase 1.7.2 + +Bug Fixes: + - Fix Python scripts to use the environment directory. + - Fix config name for "tsd.network.keep_alive" in included config files. + - Fix an issue with the filter metric and tag resolution chain during queries. + - Fix an issue with malformed, double dotted timestamps (#724). + - Fix an issue with tag filters where we need a copy before modifying the list. + - Fix comments in the config file around TCP no delay settings. + - Fix some query stats calculations around averaging and estimating the number + of data points (#784). + - Clean out old .SWO files (#821) + - Fix a live-lock situation when performing regular expression or wildcard queries (#823). + - Change the static file path for the HTTP API to be relative (#857). + - Fix an issue where the GUI could flicker when two or more tag filters were set (#708). + * Version 2.2.0 (2016-02-14) Noteworthy Changes diff --git a/THANKS b/THANKS index 08c55697a5..f31c0c88db 100644 --- a/THANKS +++ b/THANKS @@ -28,6 +28,8 @@ Cristian Sechel Christophe Furmaniak Dave Barr Davide D Amico +Dfsklar +Ethan Wang Filippo Giunchedi Gabriel Nicolas Avellaneda Guenther Schmuelling @@ -50,11 +52,13 @@ Johannes Meixner Jonathan Works Josh Thomas Kevin Bowling +Kevin Landreth Kieren Hynd Kimoon Kim Kris Beevers Kyle Brandt Lex Herbert +Li Zhe Liangliang He Liu Yubao Loïs Burg @@ -64,6 +68,7 @@ Matt Schallert Marc Tamsky Mark Smith Martin Jansen +Max Meng Michal Kimle Mike Bryant Mike Kobyakov @@ -73,6 +78,7 @@ Nikhil Benesch Nitin Aggarwal Paula Keezer Peter Gotz +Peter Edwards Ping Yong Pradeep Chhetri Rajesh G From b39763e70f66c98d9bbe7afd9aa75d1f3f96d0bc Mon Sep 17 00:00:00 2001 From: murphy66 Date: Wed, 12 Oct 2016 18:33:50 +0200 Subject: [PATCH 224/233] Fix empty array check. (#876) --- src/core/RowKey.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/RowKey.java b/src/core/RowKey.java index 694ac119c5..3563d8e566 100644 --- a/src/core/RowKey.java +++ b/src/core/RowKey.java @@ -55,7 +55,7 @@ static String metricName(final TSDB tsdb, final byte[] row) { */ public static Deferred metricNameAsync(final TSDB tsdb, final byte[] row) { - if (row == null || row.length < 0) { + if (row == null || row.length < 1) { throw new IllegalArgumentException("Row key cannot be null or empty"); } if (row.length < Const.SALT_WIDTH() + tsdb.metrics.width()) { From 6aaf5656d0128e217d3efda1d80685d7ea837bdb Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Thu, 13 Oct 2016 13:05:43 -0700 Subject: [PATCH 225/233] Fix a null tag set error in the UnionIterator. Allow for comparison operators in expressions. Booleans are now cast to 1 for true and 0 for false. Signed-off-by: Chris Larsen --- src/query/expression/ExpressionIterator.java | 10 ++- src/query/expression/UnionIterator.java | 2 +- .../expression/TestExpressionIterator.java | 74 +++++++++++++++++++ test/query/expression/TestUnionIterator.java | 5 +- 4 files changed, 87 insertions(+), 4 deletions(-) diff --git a/src/query/expression/ExpressionIterator.java b/src/query/expression/ExpressionIterator.java index 3b33d5678b..d090382bfb 100644 --- a/src/query/expression/ExpressionIterator.java +++ b/src/query/expression/ExpressionIterator.java @@ -336,7 +336,15 @@ public ExpressionDataPoint[] next(final long timestamp) { } } } - result = (Double)expression.execute(context); + final Object output = expression.execute(context); + if (output instanceof Double) { + result = (Double) expression.execute(context); + } else if (output instanceof Boolean) { + result = (((Boolean) expression.execute(context)) ? 1 : 0); + } else { + throw new IllegalStateException("Expression returned a result of type: " + + output.getClass().getName() + " for " + this); + } dps[i].reset(timestamp, result); } return dps; diff --git a/src/query/expression/UnionIterator.java b/src/query/expression/UnionIterator.java index 6453791c06..54cd70869a 100644 --- a/src/query/expression/UnionIterator.java +++ b/src/query/expression/UnionIterator.java @@ -259,7 +259,7 @@ private void setCurrentAndMeta(final ByteMap static byte[] flattenTags(final boolean use_query_tags, final boolean include_agg_tags, final ExpressionDataPoint dp, final ITimeSyncedIterator sub) { - if (dp.tags().isEmpty()) { + if (dp.tags() == null || dp.tags().isEmpty()) { return HBaseClient.EMPTY_ARRAY; } final int tagk_width = TSDB.tagk_width(); diff --git a/test/query/expression/TestExpressionIterator.java b/test/query/expression/TestExpressionIterator.java index cd90c88d31..324c1d4941 100644 --- a/test/query/expression/TestExpressionIterator.java +++ b/test/query/expression/TestExpressionIterator.java @@ -1078,6 +1078,80 @@ public void intersectionSingleSeriesIteration() throws Exception { } + @Test + public void aGreaterThanb() throws Exception { + oneExtraSameE(); + queryAB_Dstar(); + remapResults(); + + ExpressionIterator exp = new ExpressionIterator("ei", "a > b", + SetOperator.INTERSECTION, false, false); + exp.addResults("a", iterators.get("a")); + exp.addResults("b", iterators.get("b")); + + exp.compile(); + final ExpressionDataPoint[] dps = exp.values(); + assertEquals(2, dps.length); + validateMeta(dps, true); + + long ts = 1431561600000L; + double[] values = new double[] { 0, 0 }; + long its = exp.nextTimestamp(); + while (exp.hasNext()) { + exp.next(its); + + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(values[0], dps[0].toDouble(), 0.0001); + assertEquals(values[1], dps[1].toDouble(), 0.0001); + + ts += 60000; + its = exp.nextTimestamp(); + } + + for (int i = 0; i < dps.length; i++) { + assertEquals(2, dps[i].tags().size()); + assertTrue(dps[i].aggregatedTags().isEmpty()); + } + } + + @Test + public void aLessThanb() throws Exception { + oneExtraSameE(); + queryAB_Dstar(); + remapResults(); + + ExpressionIterator exp = new ExpressionIterator("ei", "a < b", + SetOperator.INTERSECTION, false, false); + exp.addResults("a", iterators.get("a")); + exp.addResults("b", iterators.get("b")); + + exp.compile(); + final ExpressionDataPoint[] dps = exp.values(); + assertEquals(2, dps.length); + validateMeta(dps, true); + + long ts = 1431561600000L; + double[] values = new double[] { 1, 1 }; + long its = exp.nextTimestamp(); + while (exp.hasNext()) { + exp.next(its); + + assertEquals(ts, dps[0].timestamp()); + assertEquals(ts, dps[1].timestamp()); + assertEquals(values[0], dps[0].toDouble(), 0.0001); + assertEquals(values[1], dps[1].toDouble(), 0.0001); + + ts += 60000; + its = exp.nextTimestamp(); + } + + for (int i = 0; i < dps.length; i++) { + assertEquals(2, dps[i].tags().size()); + assertTrue(dps[i].aggregatedTags().isEmpty()); + } + } + /** * Makes sure the series contain both metrics * @param dps The results to validate diff --git a/test/query/expression/TestUnionIterator.java b/test/query/expression/TestUnionIterator.java index 24083eefc5..77a191e7bb 100644 --- a/test/query/expression/TestUnionIterator.java +++ b/test/query/expression/TestUnionIterator.java @@ -1026,10 +1026,11 @@ public void flattenTagsQueryTagsEmptyWithAgg() throws Exception { assertArrayEquals(UID3, flat); } - @Test (expected = NullPointerException.class) + @Test public void flattenTagsNullTags() throws Exception { final ExpressionDataPoint dp = getMockDB(null, agg_tags); - UnionIterator.flattenTags(false, false, dp, sub); + final byte[] flat = UnionIterator.flattenTags(true, false, dp, sub); + assertArrayEquals(HBaseClient.EMPTY_ARRAY, flat); } @Test From aa430dc7f7cc546d9178ad5a0e5055e6b5a3f25c Mon Sep 17 00:00:00 2001 From: Haiyang Jiang Date: Wed, 12 Oct 2016 22:14:35 -0700 Subject: [PATCH 226/233] Fix up some unit tests for JDK 8 compatibility. Signed-off-by: Chris Larsen --- .travis.yml | 1 + .../expression/TestExpressionIterator.java | 12 ++++++++---- test/tsd/TestHttpJsonSerializer.java | 17 ++++++++++++----- test/tsd/TestSearchRpc.java | 9 ++------- test/tsd/TestUniqueIdRpc.java | 6 ++++-- 5 files changed, 27 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index 35b57e08a5..fccf400e41 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,5 +6,6 @@ addons: jdk: - oraclejdk7 - openjdk6 + - oraclejdk8 notifications: email: false diff --git a/test/query/expression/TestExpressionIterator.java b/test/query/expression/TestExpressionIterator.java index 324c1d4941..eaaf8ef05f 100644 --- a/test/query/expression/TestExpressionIterator.java +++ b/test/query/expression/TestExpressionIterator.java @@ -719,7 +719,8 @@ public void aPlusBOneAggedOneTaggedUseQueryTagsWoutQueryTags() throws Exception exp.compile(); final ExpressionDataPoint[] dps = exp.values(); assertEquals(1, dps.length); - validateMeta(dps, true); + // TODO - fix the TODO in the set operators to join tags + //validateMeta(dps, true); long ts = 1431561600000L; double value = 13; @@ -735,7 +736,8 @@ public void aPlusBOneAggedOneTaggedUseQueryTagsWoutQueryTags() throws Exception its = exp.nextTimestamp(); } - assertEquals(2, dps[0].tags().size()); + // TODO - fix the TODO in the set operators to join tags + //assertEquals(0, dps[0].tags().size()); assertEquals(2, dps[0].aggregatedTags().size()); assertTrue(dps[0].aggregatedTags().contains(TAGV_UIDS.get("D"))); assertTrue(dps[0].aggregatedTags().contains(TAGV_UIDS.get("E"))); @@ -899,7 +901,8 @@ public void unionOneExtraSeries() throws Exception { exp.compile(); final ExpressionDataPoint[] dps = exp.values(); assertEquals(3, dps.length); - validateMeta(dps, true); + // TODO - fix the TODO in the set operators to join tags + //validateMeta(dps, true); long ts = 1431561600000L; double[] values = new double[] { 12, 18, 17 }; @@ -921,7 +924,8 @@ public void unionOneExtraSeries() throws Exception { } for (int i = 0; i < dps.length; i++) { - assertEquals(2, dps[i].tags().size()); + // TODO - fix the TODO in the set operators to join tags + //assertEquals(2, dps[i].tags().size()); assertTrue(dps[i].aggregatedTags().isEmpty()); } assertArrayEquals(TAGV_UIDS.get("D"), dps[0].tags().get(TAGV_UIDS.get("D"))); diff --git a/test/tsd/TestHttpJsonSerializer.java b/test/tsd/TestHttpJsonSerializer.java index 6458f6096a..fc8acad09d 100644 --- a/test/tsd/TestHttpJsonSerializer.java +++ b/test/tsd/TestHttpJsonSerializer.java @@ -246,8 +246,9 @@ public void formatUidRenameV1Failed() throws Exception { map.put("error", "known"); ChannelBuffer cb = serdes.formatUidRenameV1(map); assertNotNull(cb); - assertEquals("{\"error\":\"known\",\"result\":\"false\"}", - cb.toString(Charset.forName("UTF-8"))); + final String json = cb.toString(Charset.forName("UTF-8")); + assertTrue(json.contains("\"error\":\"known\"")); + assertTrue(json.contains("\"result\":\"false\"")); } @Test (expected = IllegalArgumentException.class) @@ -262,9 +263,15 @@ public void formatSerializersV1() throws Exception { HttpQuery.initializeSerializerMaps(tsdb); HttpQuery query = NettyMocks.getQuery(tsdb, ""); HttpJsonSerializer serdes = new HttpJsonSerializer(query); - assertEquals("[{\"formatters\":", - serdes.formatSerializersV1().toString(Charset.forName("UTF-8")) - .substring(0, 15)); + + String json = serdes.formatSerializersV1().toString(Charset.forName("UTF-8")); + assertTrue(json.contains("\"request_content_type\":\"application/json\"")); + assertTrue(json.contains("\"formatters\":[")); + assertTrue(json.contains("\"response_content_type\":\"application/json; " + + "charset=UTF-8\"")); + assertTrue(json.contains("\"parsers\":[")); + assertTrue(json.contains("\"serializer\":\"json\"")); + assertTrue(json.contains("\"class\":\"net.opentsdb.tsd.HttpJsonSerializer\"")); } @Test diff --git a/test/tsd/TestSearchRpc.java b/test/tsd/TestSearchRpc.java index a881b59990..3ba1ea7d74 100644 --- a/test/tsd/TestSearchRpc.java +++ b/test/tsd/TestSearchRpc.java @@ -13,10 +13,6 @@ package net.opentsdb.tsd; import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyChar; -import static org.mockito.Matchers.anyList; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.when; import static org.powermock.api.mockito.PowerMockito.mock; import static org.junit.Assert.assertEquals; @@ -45,9 +41,7 @@ import net.opentsdb.uid.UniqueId; import net.opentsdb.uid.UniqueId.UniqueIdType; import net.opentsdb.utils.Config; -import net.opentsdb.utils.Pair; -import org.hbase.async.Bytes; import org.jboss.netty.handler.codec.http.DefaultHttpRequest; import org.jboss.netty.handler.codec.http.HttpMethod; import org.jboss.netty.handler.codec.http.HttpRequest; @@ -105,7 +99,8 @@ public void searchTSMeta_Summary() throws Exception { rpc.execute(tsdb, query); assertEquals(HttpResponseStatus.OK, query.response().getStatus()); final String result = query.response().getContent().toString(UTF); - assertTrue(result.contains("\"results\":[{\"tags\"")); + assertTrue(result.contains("\"host\":\"web01\"")); + assertTrue(result.contains("\"metric\":\"sys.cpu.0\"")); assertEquals(1, search_query.getResults().size()); } diff --git a/test/tsd/TestUniqueIdRpc.java b/test/tsd/TestUniqueIdRpc.java index d3bf95de43..6969b8f565 100644 --- a/test/tsd/TestUniqueIdRpc.java +++ b/test/tsd/TestUniqueIdRpc.java @@ -679,8 +679,10 @@ public void renameRenameException() throws Exception { "/api/uid/rename?tagv=localhost&name=localhost"); rpc.execute(tsdb, query); assertEquals(HttpResponseStatus.BAD_REQUEST, query.response().getStatus()); - assertEquals("{\"error\":\"" + message + "\",\"result\":\"false\"}", - query.response().getContent().toString(Charset.forName("UTF-8"))); + final String json = query.response().getContent() + .toString(Charset.forName("UTF-8")); + assertTrue(json.contains("\"error\":\"" + message + "\"")); + assertTrue(json.contains("\"result\":\"false\"")); } // Teset /api/uid/uidmeta -------------------- From 6102aa87a43b95f361c73ba87ba15878edbb5e58 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sun, 6 Nov 2016 12:21:30 -0800 Subject: [PATCH 227/233] Fix #887 wherein an empty groupby tag set with a non-empty non- groupby tag set was converting the non-groupbys to groupbys from the UI. Thanks @dcaillia. Also bump GWT to 2.6.1. TODO - Look at later GWT versions and allow for super dev mode as Chrome no longer supports the old dev mode. Signed-off-by: Chris Larsen --- third_party/gwt/gwt-dev-2.6.1.jar.md5 | 1 + third_party/gwt/gwt-user-2.6.1.jar.md5 | 1 + 2 files changed, 2 insertions(+) create mode 100644 third_party/gwt/gwt-dev-2.6.1.jar.md5 create mode 100644 third_party/gwt/gwt-user-2.6.1.jar.md5 diff --git a/third_party/gwt/gwt-dev-2.6.1.jar.md5 b/third_party/gwt/gwt-dev-2.6.1.jar.md5 new file mode 100644 index 0000000000..b5847e6a60 --- /dev/null +++ b/third_party/gwt/gwt-dev-2.6.1.jar.md5 @@ -0,0 +1 @@ +2f8df1f3b021315775506a1e0a4ee4b8 diff --git a/third_party/gwt/gwt-user-2.6.1.jar.md5 b/third_party/gwt/gwt-user-2.6.1.jar.md5 new file mode 100644 index 0000000000..822626d3e7 --- /dev/null +++ b/third_party/gwt/gwt-user-2.6.1.jar.md5 @@ -0,0 +1 @@ +ce17f82bb92e3a7416a9be5659cbcc89 From f6eb6d63c5cede6707b83ff6713b740229ed0b20 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Mon, 12 Dec 2016 22:42:56 -0800 Subject: [PATCH 228/233] Fix #880 by sorting the custom hash map before creating the storage JSON so that the CAS calls can complete successfully. Thanks @dpellegrino! Signed-off-by: Chris Larsen --- src/meta/Annotation.java | 13 +++++++------ test/meta/TestAnnotation.java | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/meta/Annotation.java b/src/meta/Annotation.java index cb17f84553..b595ed4057 100644 --- a/src/meta/Annotation.java +++ b/src/meta/Annotation.java @@ -19,6 +19,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.TreeMap; import org.hbase.async.Bytes; import org.hbase.async.DeleteRequest; @@ -44,6 +45,7 @@ import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.core.JsonGenerator; +import com.google.common.annotations.VisibleForTesting; import com.stumbleupon.async.Callback; import com.stumbleupon.async.Deferred; @@ -512,7 +514,8 @@ public static byte PREFIX() { * successful CAS calls * @return The serialized object as a byte array */ - private byte[] getStorageJSON() { + @VisibleForTesting + byte[] getStorageJSON() { // TODO - precalculate size final ByteArrayOutputStream output = new ByteArrayOutputStream(); try { @@ -528,11 +531,9 @@ private byte[] getStorageJSON() { if (custom == null) { json.writeNullField("custom"); } else { - json.writeObjectFieldStart("custom"); - for (Map.Entry entry : custom.entrySet()) { - json.writeStringField(entry.getKey(), entry.getValue()); - } - json.writeEndObject(); + final TreeMap sorted_custom = + new TreeMap(custom); + json.writeObjectField("custom", sorted_custom); } json.writeEndObject(); diff --git a/test/meta/TestAnnotation.java b/test/meta/TestAnnotation.java index 563ae2c2bd..3da5487744 100644 --- a/test/meta/TestAnnotation.java +++ b/test/meta/TestAnnotation.java @@ -15,8 +15,10 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import java.util.List; +import java.util.Map; import net.opentsdb.core.BaseTsdbTest; import net.opentsdb.core.Const; @@ -34,6 +36,8 @@ import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; +import com.google.common.collect.Maps; + @PowerMockIgnore({"javax.management.*", "javax.xml.*", "ch.qos.*", "org.slf4j.*", "com.sum.*", "org.xml.*"}) @@ -334,6 +338,21 @@ public void syncToStorageNoChanges() throws Exception { note.syncToStorage(tsdb, false).joinUninterruptibly(); } + @Test + public void getStorageJSONTags() throws Exception { + Map custom = Maps.newHashMap(); + custom.put("C", "C"); + custom.put("P", "P"); + custom.put("E", "E"); + System.out.println(custom); + + note.setTSUID(TSUID); + note.setStartTime(1388450562L); + note.setCustom(custom); + final String json = new String(note.getStorageJSON()); + assertTrue(json.contains("{\"C\":\"C\",\"E\":\"E\",\"P\":\"P\"}")); + } + @Test public void delete() throws Exception { setupStorage(false); From 5a9870f663f5cbe72e8401f61d54663ca57ff4d6 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Tue, 13 Dec 2016 21:20:22 -0800 Subject: [PATCH 229/233] Override the full test setup for TestFskSalted as the initialization order can change and cause UTs to fail. Signed-off-by: Chris Larsen --- test/tools/TestFsck.java | 18 ++++----- test/tools/TestFsckSalted.java | 74 +++++++++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 10 deletions(-) diff --git a/test/tools/TestFsck.java b/test/tools/TestFsck.java index 9eea0aa6a3..f99a343cf7 100644 --- a/test/tools/TestFsck.java +++ b/test/tools/TestFsck.java @@ -64,15 +64,15 @@ public class TestFsck { protected byte[] ROW2 = MockBase.stringToBytes("00000150E23510000001000001"); protected byte[] ROW3 = MockBase.stringToBytes("00000150E24320000001000001"); protected byte[] BAD_KEY = { 0x00, 0x00, 0x01 }; - private Config config; - private TSDB tsdb = null; - private HBaseClient client = mock(HBaseClient.class); - private UniqueId metrics = mock(UniqueId.class); - private UniqueId tag_names = mock(UniqueId.class); - private UniqueId tag_values = mock(UniqueId.class); - private MockBase storage; - private FsckOptions options = mock(FsckOptions.class); - private final static List tags = new ArrayList(1); + protected Config config; + protected TSDB tsdb = null; + protected HBaseClient client = mock(HBaseClient.class); + protected UniqueId metrics = mock(UniqueId.class); + protected UniqueId tag_names = mock(UniqueId.class); + protected UniqueId tag_values = mock(UniqueId.class); + protected MockBase storage; + protected FsckOptions options = mock(FsckOptions.class); + protected final static List tags = new ArrayList(1); static { tags.add(new byte[] { 0, 0, 1, 0, 0, 1}); } diff --git a/test/tools/TestFsckSalted.java b/test/tools/TestFsckSalted.java index 306dd67c7f..4ccd07ef60 100644 --- a/test/tools/TestFsckSalted.java +++ b/test/tools/TestFsckSalted.java @@ -1,17 +1,29 @@ package net.opentsdb.tools; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.when; + +import java.lang.reflect.Field; +import java.util.ArrayList; + import org.junit.Before; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; +import com.stumbleupon.async.Deferred; + import net.opentsdb.core.Const; +import net.opentsdb.core.TSDB; +import net.opentsdb.core.Tags; import net.opentsdb.storage.MockBase; +import net.opentsdb.uid.NoSuchUniqueName; +import net.opentsdb.utils.Config; @PrepareForTest({ Const.class }) public class TestFsckSalted extends TestFsck { @Before - public void beforeLocal() throws Exception { + public void before() throws Exception { PowerMockito.mockStatic(Const.class); PowerMockito.when(Const.SALT_BUCKETS()).thenReturn(2); PowerMockito.when(Const.SALT_WIDTH()).thenReturn(1); @@ -22,5 +34,65 @@ public void beforeLocal() throws Exception { ROW2 = MockBase.stringToBytes("0100000150E23510000001000001"); ROW3 = MockBase.stringToBytes("0100000150E24320000001000001"); BAD_KEY = new byte[] { 0x01, 0x00, 0x00, 0x01 }; + + config = new Config(false); + tsdb = new TSDB(client, config); + when(client.flush()).thenReturn(Deferred.fromResult(null)); + + storage = new MockBase(tsdb, client, true, true, true, true); + storage.setFamily("t".getBytes(MockBase.ASCII())); + + when(options.fix()).thenReturn(false); + when(options.compact()).thenReturn(false); + when(options.resolveDupes()).thenReturn(false); + when(options.lastWriteWins()).thenReturn(false); + when(options.deleteOrphans()).thenReturn(false); + when(options.deleteUnknownColumns()).thenReturn(false); + when(options.deleteBadValues()).thenReturn(false); + when(options.deleteBadRows()).thenReturn(false); + when(options.deleteBadCompacts()).thenReturn(false); + when(options.threads()).thenReturn(1); + + // replace the "real" field objects with mocks + Field met = tsdb.getClass().getDeclaredField("metrics"); + met.setAccessible(true); + met.set(tsdb, metrics); + + Field tagk = tsdb.getClass().getDeclaredField("tag_names"); + tagk.setAccessible(true); + tagk.set(tsdb, tag_names); + + Field tagv = tsdb.getClass().getDeclaredField("tag_values"); + tagv.setAccessible(true); + tagv.set(tsdb, tag_values); + + // mock UniqueId + when(metrics.getId("sys.cpu.user")).thenReturn(new byte[] { 0, 0, 1 }); + when(metrics.getNameAsync(new byte[] { 0, 0, 1 })) + .thenReturn(Deferred.fromResult("sys.cpu.user")); + when(metrics.getId("sys.cpu.system")) + .thenThrow(new NoSuchUniqueName("sys.cpu.system", "metric")); + when(metrics.getId("sys.cpu.nice")).thenReturn(new byte[] { 0, 0, 2 }); + when(metrics.getName(new byte[] { 0, 0, 2 })).thenReturn("sys.cpu.nice"); + when(tag_names.getId("host")).thenReturn(new byte[] { 0, 0, 1 }); + when(tag_names.getName(new byte[] { 0, 0, 1 })).thenReturn("host"); + when(tag_names.getOrCreateId("host")).thenReturn(new byte[] { 0, 0, 1 }); + when(tag_names.getId("dc")).thenThrow(new NoSuchUniqueName("dc", "metric")); + when(tag_values.getId("web01")).thenReturn(new byte[] { 0, 0, 1 }); + when(tag_values.getName(new byte[] { 0, 0, 1 })).thenReturn("web01"); + when(tag_values.getOrCreateId("web01")).thenReturn(new byte[] { 0, 0, 1 }); + when(tag_values.getId("web02")).thenReturn(new byte[] { 0, 0, 2 }); + when(tag_values.getName(new byte[] { 0, 0, 2 })).thenReturn("web02"); + when(tag_values.getOrCreateId("web02")).thenReturn(new byte[] { 0, 0, 2 }); + when(tag_values.getId("web03")) + .thenThrow(new NoSuchUniqueName("web03", "metric")); + + PowerMockito.mockStatic(Tags.class); + when(Tags.resolveIds((TSDB)any(), (ArrayList)any())) + .thenReturn(null); // don't care + + when(metrics.width()).thenReturn((short)3); + when(tag_names.width()).thenReturn((short)3); + when(tag_values.width()).thenReturn((short)3); } } From 8a3146a5128e0b70555a6d3894c65657c0273a4d Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Mon, 12 Dec 2016 23:14:00 -0800 Subject: [PATCH 230/233] Fix #868 by advancing the scanner end time by one second. That will force the scan to the next row and allow the serializer to filter out the unwanted results. Thanks @rcastbergw! Signed-off-by: Chris Larsen --- src/core/TsdbQuery.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/core/TsdbQuery.java b/src/core/TsdbQuery.java index 07a741457a..3489442d57 100644 --- a/src/core/TsdbQuery.java +++ b/src/core/TsdbQuery.java @@ -1097,6 +1097,11 @@ private long getScanEndTimeSeconds() { // Convert to seconds if we have a query in ms. if ((end & Const.SECOND_MASK) != 0L) { end /= 1000L; + if (end - (end * 1000) < 1) { + // handle an edge case where a user may request a ms time between + // 0 and 1 seconds. Just bump it a second. + end++; + } } // The calculation depends on whether we're downsampling. From 8721acd0937bb0b60ad1eafe20c8e8462e327ce7 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Sat, 17 Dec 2016 12:32:33 -0800 Subject: [PATCH 231/233] Attempt a fix for #823 wherein writes to the annotations byte map were not synchronized, potentially leading to a race condition and stuck threads. Now we'll send a fresh list to the compaction code if notes were found, synchronize on the local byte map before making any modifications. Thanks to @thatsafunnyname. Signed-off-by: Chris Larsen --- src/core/SaltScanner.java | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/core/SaltScanner.java b/src/core/SaltScanner.java index ce4e433c1a..7a7aec7376 100644 --- a/src/core/SaltScanner.java +++ b/src/core/SaltScanner.java @@ -36,6 +36,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.collect.Lists; import com.stumbleupon.async.Callback; import com.stumbleupon.async.Deferred; @@ -305,8 +306,10 @@ final class ScannerCB implements Callback kvs = new ArrayList(); private final ByteMap> annotations = new ByteMap>(); - private final Set skips = Collections.newSetFromMap(new ConcurrentHashMap()); - private final Set keepers = Collections.newSetFromMap(new ConcurrentHashMap()); + private final Set skips = Collections.newSetFromMap( + new ConcurrentHashMap()); + private final Set keepers = Collections.newSetFromMap( + new ConcurrentHashMap()); private long scanner_start = -1; /** nanosecond timestamps */ @@ -524,12 +527,6 @@ void processRow(final byte[] key, final ArrayList row) { tsdb.getClient().delete(del); } - List notes = annotations.get(key); - if (notes == null) { - notes = new ArrayList(); - annotations.put(key, notes); - } - // calculate estimated data point count. We don't want to deserialize // the byte arrays so we'll just get a rough estimate of compacted // columns. @@ -565,7 +562,18 @@ void processRow(final byte[] key, final ArrayList row) { // the scanner final long compaction_start = DateTime.nanoTime(); try { + final List notes = Lists.newArrayList(); compacted = tsdb.compact(row, notes); + if (!notes.isEmpty()) { + synchronized (annotations) { + List map_notes = annotations.get(key); + if (map_notes == null) { + annotations.put(key, notes); + } else { + map_notes.addAll(notes); + } + } + } } catch (IllegalDataException idex) { compaction_time += (DateTime.nanoTime() - compaction_start); close(false); From 039bc41cc08af79d4c5e948ec4da0d6b54cc5c61 Mon Sep 17 00:00:00 2001 From: Bryan Hernandez Date: Tue, 20 Dec 2016 14:48:11 -0800 Subject: [PATCH 232/233] Add example classes for queries and writing data. Signed-off-by: Chris Larsen --- src/examples/AddDataExample.java | 174 +++++++++++++++++++++++++++++++ src/examples/QueryExample.java | 166 +++++++++++++++++++++++++++++ 2 files changed, 340 insertions(+) create mode 100644 src/examples/AddDataExample.java create mode 100644 src/examples/QueryExample.java diff --git a/src/examples/AddDataExample.java b/src/examples/AddDataExample.java new file mode 100644 index 0000000000..a2f951fb39 --- /dev/null +++ b/src/examples/AddDataExample.java @@ -0,0 +1,174 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2015 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . + +package net.opentsdb.examples; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +import com.stumbleupon.async.Callback; +import com.stumbleupon.async.Deferred; + +import net.opentsdb.core.TSDB; +import net.opentsdb.uid.NoSuchUniqueName; +import net.opentsdb.uid.UniqueId.UniqueIdType; +import net.opentsdb.utils.Config; + +/** + * Examples for how to add points to the tsdb. + * + */ +public class AddDataExample { + private static String pathToConfigFile; + + public static void processArgs(String[] args) { + // Set these as arguments so you don't have to keep path information in + // source files + if (args == null) { + System.err.println("First (and only) argument must be the full path to the opentsdb.conf file. (e.g. /User/thisUser/opentsdb/src/opentsdb.conf"); + } else { + pathToConfigFile = args[0]; + } + } + + public static void main(String[] args) throws Exception { + + + + processArgs(args); + + // Create a config object with a path to the file for parsing. Or manually + // override settings. + // e.g. config.overrideConfig("tsd.storage.hbase.zk_quorum", "localhost"); + Config config = new Config(pathToConfigFile); + final TSDB tsdb = new TSDB(config); + + // Declare new metric + String metricName = "dummyFromjavaAPI"; + // First check to see it doesn't already exist + byte[] byteMetricUID; // we don't actually need this for the first + // .addPoint() call below. + // TODO: Ideally we could just call a not-yet-implemented tsdb.uIdExists() + // function. + // Note, however, that this is optional. If autometric is enabled, the UID will be assigned in call to addPoint(). + try { + byteMetricUID = tsdb.getUID(UniqueIdType.METRIC, metricName); + } catch (IllegalArgumentException iae) { + System.out.println("Metric name not valid."); + iae.printStackTrace(); + System.exit(1); + } catch (NoSuchUniqueName nsune) { + // If not, great. Create it. + byteMetricUID = tsdb.assignUid("metric", metricName); + } + + // Make a single datum + long timestamp = System.currentTimeMillis(); + long value = 314159; + // Make key-val + Map tags = new HashMap(1); + tags.put("dummy-key", "dummy-val1"); + + + + + // Start timer + long startTime1 = System.currentTimeMillis(); + + int n = 100; + ArrayList> deferreds = new ArrayList>(n); + for (int i = 0; i < n; i++) { + Deferred deferred = tsdb.addPoint(metricName, timestamp, value + i, tags); + deferreds.add(deferred); + + } + + // Add the callbacks to the deferred object. (They might have already + // returned, btw) + // This will cause the calling thread to wait until the add has completed. + + System.out.println("Waiting for deferred result to return..."); + Deferred.groupInOrder(deferreds) + .addErrback(new AddDataExample().new errBack()) + .addCallback(new AddDataExample().new succBack()) + .join(); + + // Block the thread until the deferred returns it's result. +// deferred.join(); + + // End timer. + long elapsedTime1 = System.currentTimeMillis() - startTime1; + System.out.println("\nAdding " + n + " points took: " + elapsedTime1 + + " milliseconds.\n"); + + + // Gracefully shutdown connection to TSDB + tsdb.shutdown(); + + + } + + // This is an optional errorback to handle when there is a failure. + class errBack implements Callback { + public String call(final Exception e) throws Exception { + String message = ">>>>>>>>>>>Failure!>>>>>>>>>>>"; + System.err.println(message); + return message; + } + }; + + // This is an optional success callback to handle when there is a success. + class succBack implements Callback> { + public Object call(ArrayList results) { + for (Object res : results) { + if (res != null) { + if (res.toString().equals("MultiActionSuccess")) { + System.err.println(">>>>>>>>>>>Success!>>>>>>>>>>>"); + } + } else { + System.err.println(">>>>>>>>>>>" + res.getClass() + ">>>>>>>>>>>"); + } + } + return null; + } + }; + + public static long[] makeRandomValues(int num, Random rand) { + long[] values = new long[num]; + for (int i = 0; i < num; i++) { + // define datum + values[i] = rand.nextInt(); + } + return values; + } + + public static long[] makeTimestamps(int num, Random rand) { + + long[] timestamps = new long[num]; + for (int i = 0; i < num; i++) { + // ensures that we won't try to put two data points in the same + // millisecond + try { + Thread.sleep(1); // in millis + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + // define datum + timestamps[i] = System.currentTimeMillis(); + } + return timestamps; + } + +} diff --git a/src/examples/QueryExample.java b/src/examples/QueryExample.java new file mode 100644 index 0000000000..fc053ea46b --- /dev/null +++ b/src/examples/QueryExample.java @@ -0,0 +1,166 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2010-2012 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.examples; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import com.stumbleupon.async.Callback; +import com.stumbleupon.async.Deferred; + +import net.opentsdb.core.DataPoint; +import net.opentsdb.core.DataPoints; +import net.opentsdb.core.Query; +import net.opentsdb.core.SeekableView; +import net.opentsdb.core.TSDB; +import net.opentsdb.core.TSQuery; +import net.opentsdb.core.TSSubQuery; +import net.opentsdb.utils.Config; + +/** + * One example on how to query. + * Taken from this thread + * The metric and key query arguments assume that you've input data from the Quick Start tutorial + * here. + */ +public class QueryExample { + + public static void main(String[] args) throws IOException { + + // Set these as arguments so you don't have to keep path information in + // source files + String pathToConfigFile = args[0]; // e.g. "/User/thisUser/opentsdb/src/opentsdb.conf" + String hostValue = args[1]; // e.g. "myComputerName" + + // Create a config object with a path to the file for parsing. Or manually + // override settings. + // e.g. config.overrideConfig("tsd.storage.hbase.zk_quorum", "localhost"); + Config config; + config = new Config(pathToConfigFile); + final TSDB tsdb = new TSDB(config); + + // main query + final TSQuery query = new TSQuery(); + // use any string format from + // http://opentsdb.net/docs/build/html/user_guide/query/dates.html + query.setStart("1h-ago"); + // Optional: set other global query params + + // at least one sub query required. This is where you specify the metric and + // tags + final TSSubQuery subQuery = new TSSubQuery(); + subQuery.setMetric("proc.loadavg.1m"); + + // tags are optional but you can create and populate a map + final HashMap tags = new HashMap(1); + tags.put("host", hostValue); + subQuery.setTags(tags); + + // you do have to set an aggregator. Just provide the name as a string + subQuery.setAggregator("sum"); + + // IMPORTANT: don't forget to add the subQuery + final ArrayList subQueries = new ArrayList(1); + subQueries.add(subQuery); + query.setQueries(subQueries); + query.setMsResolution(true); // otherwise we aggregate on the second. + + // make sure the query is valid. This will throw exceptions if something + // is missing + query.validateAndSetQuery(); + + // compile the queries into TsdbQuery objects behind the scenes + Query[] tsdbqueries = query.buildQueries(tsdb); + + // create some arrays for storing the results and the async calls + final int nqueries = tsdbqueries.length; + final ArrayList results = new ArrayList( + nqueries); + final ArrayList> deferreds = new ArrayList>( + nqueries); + + // this executes each of the sub queries asynchronously and puts the + // deferred in an array so we can wait for them to complete. + for (int i = 0; i < nqueries; i++) { + deferreds.add(tsdbqueries[i].runAsync()); + } + + // Start timer + long startTime = System.nanoTime(); + + // This is a required callback class to store the results after each + // query has finished + class QueriesCB implements Callback> { + public Object call(final ArrayList queryResults) + throws Exception { + results.addAll(queryResults); + return null; + } + } + + // this will cause the calling thread to wait until ALL of the queries + // have completed. + try { + Deferred.groupInOrder(deferreds).addCallback(new QueriesCB()) + .joinUninterruptibly(); + } catch (Exception e) { + e.printStackTrace(); + } + + // End timer. + long elapsedTime = (System.nanoTime() - startTime) / (1000*1000); + System.out.println("Query returned in: " + elapsedTime + " milliseconds."); + + // now all of the results are in so we just iterate over each set of + // results and do any processing necessary. + for (final DataPoints[] dataSets : results) { + for (final DataPoints data : dataSets) { + System.out.print(data.metricName()); + Map resolvedTags = data.getTags(); + for (final Map.Entry pair : resolvedTags.entrySet()) { + System.out.print(" " + pair.getKey() + "=" + pair.getValue()); + } + System.out.print("\n"); + + final SeekableView it = data.iterator(); + /* + * An important point about SeekableView: + * Because no data is copied during iteration and no new object gets + * created, the DataPoint returned must not be stored and gets + * invalidated as soon as next is called on the iterator (actually it + * doesn't get invalidated but rather its contents changes). If you want + * to store individual data points, you need to copy the timestamp and + * value out of each DataPoint into your own data structures. + * + * In the vast majority of cases, the iterator will be used to go once + * through all the data points, which is why it's not a problem if the + * iterator acts just as a transient "view". Iterating will be very + * cheap since no memory allocation is required (except to instantiate + * the actual iterator at the beginning). + */ + while (it.hasNext()) { + final DataPoint dp = it.next(); + System.out.println(" " + dp.timestamp() + " " + + (dp.isInteger() ? dp.longValue() : dp.doubleValue())); + } + System.out.println(""); + + // Gracefully shutdown connection to TSDB + tsdb.shutdown(); + } + } + } + +} \ No newline at end of file From 8993ccf297c702093ff7caec22aa2bd041350842 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Tue, 20 Dec 2016 17:08:44 -0800 Subject: [PATCH 233/233] Cleanup the examples a bit and update the query example with filters from 2.2. Signed-off-by: Chris Larsen --- src/examples/AddDataExample.java | 97 +++++++++++--------------------- src/examples/QueryExample.java | 80 ++++++++++++++++++-------- 2 files changed, 88 insertions(+), 89 deletions(-) diff --git a/src/examples/AddDataExample.java b/src/examples/AddDataExample.java index a2f951fb39..7c16c69ac3 100644 --- a/src/examples/AddDataExample.java +++ b/src/examples/AddDataExample.java @@ -16,7 +16,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Map; -import java.util.Random; import com.stumbleupon.async.Callback; import com.stumbleupon.async.Deferred; @@ -33,36 +32,39 @@ public class AddDataExample { private static String pathToConfigFile; - public static void processArgs(String[] args) { - // Set these as arguments so you don't have to keep path information in + public static void processArgs(final String[] args) { + // Set these as arguments so you don't have to keep path information in // source files - if (args == null) { - System.err.println("First (and only) argument must be the full path to the opentsdb.conf file. (e.g. /User/thisUser/opentsdb/src/opentsdb.conf"); - } else { + if (args != null && args.length > 0) { pathToConfigFile = args[0]; } } - public static void main(String[] args) throws Exception { - - - + public static void main(final String[] args) throws Exception { processArgs(args); // Create a config object with a path to the file for parsing. Or manually // override settings. // e.g. config.overrideConfig("tsd.storage.hbase.zk_quorum", "localhost"); - Config config = new Config(pathToConfigFile); + final Config config; + if (pathToConfigFile != null && !pathToConfigFile.isEmpty()) { + config = new Config(pathToConfigFile); + } else { + // Search for a default config from /etc/opentsdb/opentsdb.conf, etc. + config = new Config(true); + } final TSDB tsdb = new TSDB(config); // Declare new metric - String metricName = "dummyFromjavaAPI"; + String metricName = "my.tsdb.test.metric"; // First check to see it doesn't already exist byte[] byteMetricUID; // we don't actually need this for the first // .addPoint() call below. // TODO: Ideally we could just call a not-yet-implemented tsdb.uIdExists() // function. - // Note, however, that this is optional. If autometric is enabled, the UID will be assigned in call to addPoint(). + // Note, however, that this is optional. If auto metric is enabled + // (tsd.core.auto_create_metrics), the UID will be assigned in call to + // addPoint(). try { byteMetricUID = tsdb.getUID(UniqueIdType.METRIC, metricName); } catch (IllegalArgumentException iae) { @@ -75,100 +77,65 @@ public static void main(String[] args) throws Exception { } // Make a single datum - long timestamp = System.currentTimeMillis(); + long timestamp = System.currentTimeMillis() / 1000; long value = 314159; // Make key-val Map tags = new HashMap(1); - tags.put("dummy-key", "dummy-val1"); - - - + tags.put("script", "example1"); // Start timer long startTime1 = System.currentTimeMillis(); + // Write a number of data points at 30 second intervals. Each write will + // return a deferred (similar to a Java Future or JS Promise) that will + // be called on completion with either a "null" value on success or an + // exception. int n = 100; ArrayList> deferreds = new ArrayList>(n); for (int i = 0; i < n; i++) { Deferred deferred = tsdb.addPoint(metricName, timestamp, value + i, tags); deferreds.add(deferred); - + timestamp += 30; } // Add the callbacks to the deferred object. (They might have already // returned, btw) // This will cause the calling thread to wait until the add has completed. - System.out.println("Waiting for deferred result to return..."); Deferred.groupInOrder(deferreds) .addErrback(new AddDataExample().new errBack()) .addCallback(new AddDataExample().new succBack()) + // Block the thread until the deferred returns it's result. .join(); - - // Block the thread until the deferred returns it's result. -// deferred.join(); + // Alternatively you can add another callback here or use a join with a + // timeout argument. // End timer. long elapsedTime1 = System.currentTimeMillis() - startTime1; System.out.println("\nAdding " + n + " points took: " + elapsedTime1 + " milliseconds.\n"); - - // Gracefully shutdown connection to TSDB - tsdb.shutdown(); - - + // Gracefully shutdown connection to TSDB. This is CRITICAL as it will + // flush any pending operations to HBase. + tsdb.shutdown().join(); } // This is an optional errorback to handle when there is a failure. class errBack implements Callback { public String call(final Exception e) throws Exception { String message = ">>>>>>>>>>>Failure!>>>>>>>>>>>"; - System.err.println(message); + System.err.println(message + " " + e.getMessage()); + e.printStackTrace(); return message; } }; // This is an optional success callback to handle when there is a success. class succBack implements Callback> { - public Object call(ArrayList results) { - for (Object res : results) { - if (res != null) { - if (res.toString().equals("MultiActionSuccess")) { - System.err.println(">>>>>>>>>>>Success!>>>>>>>>>>>"); - } - } else { - System.err.println(">>>>>>>>>>>" + res.getClass() + ">>>>>>>>>>>"); - } - } + public Object call(final ArrayList results) { + System.out.println("Successfully wrote " + results.size() + " data points"); return null; } }; - - public static long[] makeRandomValues(int num, Random rand) { - long[] values = new long[num]; - for (int i = 0; i < num; i++) { - // define datum - values[i] = rand.nextInt(); - } - return values; - } - - public static long[] makeTimestamps(int num, Random rand) { - - long[] timestamps = new long[num]; - for (int i = 0; i < num; i++) { - // ensures that we won't try to put two data points in the same - // millisecond - try { - Thread.sleep(1); // in millis - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } - // define datum - timestamps[i] = System.currentTimeMillis(); - } - return timestamps; - } } diff --git a/src/examples/QueryExample.java b/src/examples/QueryExample.java index fc053ea46b..d03b23ea7c 100644 --- a/src/examples/QueryExample.java +++ b/src/examples/QueryExample.java @@ -1,5 +1,5 @@ // This file is part of OpenTSDB. -// Copyright (C) 2010-2012 The OpenTSDB Authors. +// Copyright (C) 2015 The OpenTSDB Authors. // // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published by @@ -14,7 +14,7 @@ import java.io.IOException; import java.util.ArrayList; -import java.util.HashMap; +import java.util.List; import java.util.Map; import com.stumbleupon.async.Callback; @@ -27,32 +27,41 @@ import net.opentsdb.core.TSDB; import net.opentsdb.core.TSQuery; import net.opentsdb.core.TSSubQuery; +import net.opentsdb.query.filter.TagVFilter; import net.opentsdb.utils.Config; +import net.opentsdb.utils.DateTime; /** * One example on how to query. - * Taken from this thread + * Taken from + * + * this thread * The metric and key query arguments assume that you've input data from the Quick Start tutorial * here. */ public class QueryExample { - public static void main(String[] args) throws IOException { + public static void main(final String[] args) throws IOException { // Set these as arguments so you don't have to keep path information in // source files - String pathToConfigFile = args[0]; // e.g. "/User/thisUser/opentsdb/src/opentsdb.conf" - String hostValue = args[1]; // e.g. "myComputerName" + String pathToConfigFile = (args != null && args.length > 0 ? args[0] : null); // Create a config object with a path to the file for parsing. Or manually // override settings. // e.g. config.overrideConfig("tsd.storage.hbase.zk_quorum", "localhost"); - Config config; - config = new Config(pathToConfigFile); + final Config config; + if (pathToConfigFile != null && !pathToConfigFile.isEmpty()) { + config = new Config(pathToConfigFile); + } else { + // Search for a default config from /etc/opentsdb/opentsdb.conf, etc. + config = new Config(true); + } final TSDB tsdb = new TSDB(config); // main query final TSQuery query = new TSQuery(); + // use any string format from // http://opentsdb.net/docs/build/html/user_guide/query/dates.html query.setStart("1h-ago"); @@ -61,13 +70,18 @@ public static void main(String[] args) throws IOException { // at least one sub query required. This is where you specify the metric and // tags final TSSubQuery subQuery = new TSSubQuery(); - subQuery.setMetric("proc.loadavg.1m"); - - // tags are optional but you can create and populate a map - final HashMap tags = new HashMap(1); - tags.put("host", hostValue); - subQuery.setTags(tags); - + subQuery.setMetric("my.tsdb.test.metric"); + + // filters are optional but useful. + final List filters = new ArrayList(1); + filters.add(new TagVFilter.Builder() + .setType("literal_or") + .setFilter("example1") + .setTagk("script") + .setGroupBy(true) + .build()); + subQuery.setFilters(filters); + // you do have to set an aggregator. Just provide the name as a string subQuery.setAggregator("sum"); @@ -88,8 +102,8 @@ public static void main(String[] args) throws IOException { final int nqueries = tsdbqueries.length; final ArrayList results = new ArrayList( nqueries); - final ArrayList> deferreds = new ArrayList>( - nqueries); + final ArrayList> deferreds = + new ArrayList>(nqueries); // this executes each of the sub queries asynchronously and puts the // deferred in an array so we can wait for them to complete. @@ -98,7 +112,7 @@ public static void main(String[] args) throws IOException { } // Start timer - long startTime = System.nanoTime(); + long startTime = DateTime.nanoTime(); // This is a required callback class to store the results after each // query has finished @@ -109,18 +123,30 @@ public Object call(final ArrayList queryResults) return null; } } + + // Make sure to handle any errors that might crop up + class QueriesEB implements Callback { + @Override + public Object call(final Exception e) throws Exception { + System.err.println("Queries failed"); + e.printStackTrace(); + return null; + } + } // this will cause the calling thread to wait until ALL of the queries // have completed. try { - Deferred.groupInOrder(deferreds).addCallback(new QueriesCB()) - .joinUninterruptibly(); + Deferred.groupInOrder(deferreds) + .addCallback(new QueriesCB()) + .addErrback(new QueriesEB()) + .join(); } catch (Exception e) { e.printStackTrace(); } // End timer. - long elapsedTime = (System.nanoTime() - startTime) / (1000*1000); + double elapsedTime = DateTime.msFromNanoDiff(DateTime.nanoTime(), startTime); System.out.println("Query returned in: " + elapsedTime + " milliseconds."); // now all of the results are in so we just iterate over each set of @@ -156,11 +182,17 @@ public Object call(final ArrayList queryResults) + (dp.isInteger() ? dp.longValue() : dp.doubleValue())); } System.out.println(""); - - // Gracefully shutdown connection to TSDB - tsdb.shutdown(); } } + + // Gracefully shutdown connection to TSDB + try { + tsdb.shutdown().join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } } } \ No newline at end of file