mirror of https://github.com/apache/druid.git
Merge pull request #292 from metamx/javascript-postaggregator
JavaScript post-aggregator
This commit is contained in:
commit
61afc542d6
|
@ -40,6 +40,31 @@ The constant post-aggregator always returns the specified value.
|
||||||
{ "type" : "constant", "name" : <output_name>, "value" : <numerical_value> }
|
{ "type" : "constant", "name" : <output_name>, "value" : <numerical_value> }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### JavaScript post-aggregator
|
||||||
|
|
||||||
|
Applies the provided JavaScript function to the given fields. Fields are passed as arguments to the JavaScript function in the given order.
|
||||||
|
|
||||||
|
```json
|
||||||
|
postAggregation : {
|
||||||
|
"type": "javascript",
|
||||||
|
"name": <output_name>,
|
||||||
|
"fieldNames" : [<aggregator_name>, <aggregator_name>, ...],
|
||||||
|
"function": <javascript function>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Example JavaScript aggregator:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "javascript",
|
||||||
|
"name": "absPercent",
|
||||||
|
"fieldNames": ["delta", "total"],
|
||||||
|
"function": "function(delta, total) { return 100 * Math.abs(delta) / total; }"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Example Usage
|
### Example Usage
|
||||||
|
|
||||||
In this example, let’s calculate a simple percentage using post aggregators. Let’s imagine our data set has a metric called "total".
|
In this example, let’s calculate a simple percentage using post aggregators. Let’s imagine our data set has a metric called "total".
|
||||||
|
|
|
@ -34,6 +34,7 @@ import io.druid.query.aggregation.PostAggregator;
|
||||||
import io.druid.query.aggregation.post.ArithmeticPostAggregator;
|
import io.druid.query.aggregation.post.ArithmeticPostAggregator;
|
||||||
import io.druid.query.aggregation.post.ConstantPostAggregator;
|
import io.druid.query.aggregation.post.ConstantPostAggregator;
|
||||||
import io.druid.query.aggregation.post.FieldAccessPostAggregator;
|
import io.druid.query.aggregation.post.FieldAccessPostAggregator;
|
||||||
|
import io.druid.query.aggregation.post.JavaScriptPostAggregator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
|
@ -63,7 +64,8 @@ public class AggregatorsModule extends SimpleModule
|
||||||
@JsonSubTypes(value = {
|
@JsonSubTypes(value = {
|
||||||
@JsonSubTypes.Type(name = "arithmetic", value = ArithmeticPostAggregator.class),
|
@JsonSubTypes.Type(name = "arithmetic", value = ArithmeticPostAggregator.class),
|
||||||
@JsonSubTypes.Type(name = "fieldAccess", value = FieldAccessPostAggregator.class),
|
@JsonSubTypes.Type(name = "fieldAccess", value = FieldAccessPostAggregator.class),
|
||||||
@JsonSubTypes.Type(name = "constant", value = ConstantPostAggregator.class)
|
@JsonSubTypes.Type(name = "constant", value = ConstantPostAggregator.class),
|
||||||
|
@JsonSubTypes.Type(name = "javascript", value = JavaScriptPostAggregator.class)
|
||||||
})
|
})
|
||||||
public static interface PostAggregatorMixin {}
|
public static interface PostAggregatorMixin {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
/*
|
||||||
|
* Druid - a distributed column store.
|
||||||
|
* Copyright (C) 2012, 2013 Metamarkets Group Inc.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.druid.query.aggregation.post;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import io.druid.query.aggregation.PostAggregator;
|
||||||
|
import org.mozilla.javascript.Context;
|
||||||
|
import org.mozilla.javascript.ContextFactory;
|
||||||
|
import org.mozilla.javascript.ScriptableObject;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
|
||||||
|
public class JavaScriptPostAggregator implements PostAggregator
|
||||||
|
{
|
||||||
|
private static final Comparator COMPARATOR = new Comparator()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public int compare(Object o, Object o1)
|
||||||
|
{
|
||||||
|
return ((Double) o).compareTo((Double) o1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static interface Function
|
||||||
|
{
|
||||||
|
public double apply(final Object[] args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Function compile(String function) {
|
||||||
|
final ContextFactory contextFactory = ContextFactory.getGlobal();
|
||||||
|
final Context context = contextFactory.enterContext();
|
||||||
|
context.setOptimizationLevel(9);
|
||||||
|
|
||||||
|
final ScriptableObject scope = context.initStandardObjects();
|
||||||
|
|
||||||
|
final org.mozilla.javascript.Function fn = context.compileFunction(scope, function, "aggregate", 1, null);
|
||||||
|
Context.exit();
|
||||||
|
|
||||||
|
|
||||||
|
return new Function()
|
||||||
|
{
|
||||||
|
public double apply(Object[] args)
|
||||||
|
{
|
||||||
|
// ideally we need a close() function to discard the context once it is not used anymore
|
||||||
|
Context cx = Context.getCurrentContext();
|
||||||
|
if (cx == null) {
|
||||||
|
cx = contextFactory.enterContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Context.toNumber(fn.call(cx, scope, scope, args));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final List<String> fieldNames;
|
||||||
|
private final String function;
|
||||||
|
|
||||||
|
private final Function fn;
|
||||||
|
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public JavaScriptPostAggregator(
|
||||||
|
@JsonProperty("name") String name,
|
||||||
|
@JsonProperty("fieldNames") final List<String> fieldNames,
|
||||||
|
@JsonProperty("function") final String function
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Preconditions.checkNotNull(name, "Must have a valid, non-null post-aggregator name");
|
||||||
|
Preconditions.checkNotNull(fieldNames, "Must have a valid, non-null fieldNames");
|
||||||
|
Preconditions.checkNotNull(function, "Must have a valid, non-null function");
|
||||||
|
|
||||||
|
this.name = name;
|
||||||
|
this.fieldNames = fieldNames;
|
||||||
|
this.function = function;
|
||||||
|
|
||||||
|
this.fn = compile(function);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getDependentFields()
|
||||||
|
{
|
||||||
|
return Sets.newHashSet(fieldNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Comparator getComparator()
|
||||||
|
{
|
||||||
|
return COMPARATOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object compute(Map<String, Object> combinedAggregators)
|
||||||
|
{
|
||||||
|
final Object[] args = new Object[fieldNames.size()];
|
||||||
|
int i = 0;
|
||||||
|
for(String field : fieldNames) {
|
||||||
|
args[i++] = combinedAggregators.get(field);
|
||||||
|
}
|
||||||
|
return fn.apply(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
@Override
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public List<String> getFieldNames()
|
||||||
|
{
|
||||||
|
return fieldNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public String getFunction()
|
||||||
|
{
|
||||||
|
return function;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* Druid - a distributed column store.
|
||||||
|
* Copyright (C) 2012, 2013 Metamarkets Group Inc.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.druid.query.aggregation.post;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class JavaScriptPostAggregatorTest
|
||||||
|
{
|
||||||
|
@Test
|
||||||
|
public void testCompute()
|
||||||
|
{
|
||||||
|
JavaScriptPostAggregator javaScriptPostAggregator;
|
||||||
|
|
||||||
|
Map<String, Object> metricValues = Maps.newHashMap();
|
||||||
|
metricValues.put("delta", -10.0);
|
||||||
|
metricValues.put("total", 100.0);
|
||||||
|
|
||||||
|
|
||||||
|
String absPercentFunction = "function(delta, total) { return 100 * Math.abs(delta) / total; }";
|
||||||
|
javaScriptPostAggregator = new JavaScriptPostAggregator("absPercent", Lists.newArrayList("delta", "total"), absPercentFunction);
|
||||||
|
|
||||||
|
Assert.assertEquals(10.0, javaScriptPostAggregator.compute(metricValues));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue