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> }
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
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.ConstantPostAggregator;
|
||||
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.Type(name = "arithmetic", value = ArithmeticPostAggregator.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 {}
|
||||
}
|
||||
|
|
|
@ -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