Browse Source

Add value_count mode to rate agg (#63687)

Adds a new value count mode to the rate aggregation.

Closes #63575
Igor Motov 5 năm trước cách đây
mục cha
commit
e6c70f6811

+ 80 - 2
docs/reference/aggregations/metrics/rate-aggregation.asciidoc

@@ -93,8 +93,8 @@ be automatically calculated by multiplying monthly rate by 12.
 // TESTRESPONSE[s/\.\.\./"took": $body.took,"timed_out": false,"_shards": $body._shards,"hits": $body.hits,/]
 
 Instead of counting the number of documents, it is also possible to calculate a sum of all values of the fields in the documents in each
-bucket. The following request will group all sales records into monthly bucket and than calculate the total monthly sales and convert them
-into average daily sales.
+bucket or the number of values in each bucket. The following request will group all sales records into monthly bucket and than calculate
+the total monthly sales and convert them into average daily sales.
 
 [source,console]
 --------------------------------------------------
@@ -164,6 +164,84 @@ The response will contain the average daily sale prices for each month.
 --------------------------------------------------
 // TESTRESPONSE[s/\.\.\./"took": $body.took,"timed_out": false,"_shards": $body._shards,"hits": $body.hits,/]
 
+By adding the `mode` parameter with the value `value_count`, we can change the calculation from `sum` to the number of values of the field:
+
+[source,console]
+--------------------------------------------------
+GET sales/_search
+{
+  "size": 0,
+  "aggs": {
+    "by_date": {
+      "date_histogram": {
+        "field": "date",
+        "calendar_interval": "month"  <1>
+      },
+      "aggs": {
+        "avg_number_of_sales_per_year": {
+          "rate": {
+            "field": "price", <2>
+            "unit": "year",  <3>
+            "mode": "value_count" <4>
+          }
+        }
+      }
+    }
+  }
+}
+--------------------------------------------------
+// TEST[setup:sales]
+<1> Histogram is grouped by month.
+<2> Calculate number of of all sale prices
+<3> Convert to annual counts
+<4> Changing the mode to value count
+
+The response will contain the average daily sale prices for each month.
+
+[source,console-result]
+--------------------------------------------------
+{
+  ...
+  "aggregations" : {
+    "by_date" : {
+      "buckets" : [
+        {
+          "key_as_string" : "2015/01/01 00:00:00",
+          "key" : 1420070400000,
+          "doc_count" : 3,
+          "avg_number_of_sales_per_year" : {
+            "value" : 36.0
+          }
+        },
+        {
+          "key_as_string" : "2015/02/01 00:00:00",
+          "key" : 1422748800000,
+          "doc_count" : 2,
+          "avg_number_of_sales_per_year" : {
+            "value" : 24.0
+          }
+        },
+        {
+          "key_as_string" : "2015/03/01 00:00:00",
+          "key" : 1425168000000,
+          "doc_count" : 2,
+          "avg_number_of_sales_per_year" : {
+            "value" : 24.0
+          }
+        }
+      ]
+    }
+  }
+}
+--------------------------------------------------
+// TESTRESPONSE[s/\.\.\./"took": $body.took,"timed_out": false,"_shards": $body._shards,"hits": $body.hits,/]
+
+By default `sum` mode is used.
+
+`"mode": "sum"`:: calculate the sum of all values field
+`"mode": "value_count"`:: use the number of values in the field
+
+The `mode` parameter can only be used with fields and scripts.
 
 ==== Relationship between bucket sizes and rate
 

+ 6 - 0
x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/AbstractRateAggregator.java

@@ -26,6 +26,7 @@ public abstract class AbstractRateAggregator extends NumericMetricsAggregator.Si
     protected final ValuesSource valuesSource;
     private final DocValueFormat format;
     private final Rounding.DateTimeUnit rateUnit;
+    protected final RateMode rateMode;
     private final SizedBucketAggregator sizedBucketAggregator;
 
     protected DoubleArray sums;
@@ -35,6 +36,7 @@ public abstract class AbstractRateAggregator extends NumericMetricsAggregator.Si
         String name,
         ValuesSourceConfig valuesSourceConfig,
         Rounding.DateTimeUnit rateUnit,
+        RateMode rateMode,
         SearchContext context,
         Aggregator parent,
         Map<String, Object> metadata
@@ -45,8 +47,12 @@ public abstract class AbstractRateAggregator extends NumericMetricsAggregator.Si
         if (valuesSource != null) {
             sums = context.bigArrays().newDoubleArray(1, true);
             compensations = context.bigArrays().newDoubleArray(1, true);
+            if (rateMode == null) {
+                rateMode = RateMode.SUM;
+            }
         }
         this.rateUnit = rateUnit;
+        this.rateMode = rateMode;
         this.sizedBucketAggregator = findSizedBucketAncestor();
     }
 

+ 14 - 2
x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/HistogramRateAggregator.java

@@ -27,11 +27,12 @@ public class HistogramRateAggregator extends AbstractRateAggregator {
         String name,
         ValuesSourceConfig valuesSourceConfig,
         Rounding.DateTimeUnit rateUnit,
+        RateMode rateMode,
         SearchContext context,
         Aggregator parent,
         Map<String, Object> metadata
     ) throws IOException {
-        super(name, valuesSourceConfig, rateUnit, context, parent, metadata);
+        super(name, valuesSourceConfig, rateUnit, rateMode, context, parent, metadata);
     }
 
     @Override
@@ -51,7 +52,18 @@ public class HistogramRateAggregator extends AbstractRateAggregator {
                         double sum = sums.get(bucket);
                         double compensation = compensations.get(bucket);
                         kahanSummation.reset(sum, compensation);
-                        kahanSummation.add(sketch.value());
+                        final double value;
+                        switch (rateMode) {
+                            case SUM:
+                                value = sketch.value();
+                                break;
+                            case VALUE_COUNT:
+                                value = sketch.count();
+                                break;
+                            default:
+                                throw new IllegalArgumentException("Unsupported rate mode " + rateMode);
+                        }
+                        kahanSummation.add(value);
                         compensations.set(bucket, kahanSummation.delta());
                         sums.set(bucket, kahanSummation.value());
                     }

+ 13 - 5
x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/NumericRateAggregator.java

@@ -26,11 +26,12 @@ public class NumericRateAggregator extends AbstractRateAggregator {
         String name,
         ValuesSourceConfig valuesSourceConfig,
         Rounding.DateTimeUnit rateUnit,
+        RateMode rateMode,
         SearchContext context,
         Aggregator parent,
         Map<String, Object> metadata
     ) throws IOException {
-        super(name, valuesSourceConfig, rateUnit, context, parent, metadata);
+        super(name, valuesSourceConfig, rateUnit, rateMode, context, parent, metadata);
     }
 
     @Override
@@ -51,10 +52,17 @@ public class NumericRateAggregator extends AbstractRateAggregator {
                     double sum = sums.get(bucket);
                     double compensation = compensations.get(bucket);
                     kahanSummation.reset(sum, compensation);
-
-                    for (int i = 0; i < valuesCount; i++) {
-                        double value = values.nextValue();
-                        kahanSummation.add(value);
+                    switch (rateMode) {
+                        case SUM:
+                            for (int i = 0; i < valuesCount; i++) {
+                                kahanSummation.add(values.nextValue());
+                            }
+                            break;
+                        case VALUE_COUNT:
+                            kahanSummation.add(valuesCount);
+                            break;
+                        default:
+                            throw new IllegalArgumentException("Unsupported rate mode " + rateMode);
                     }
 
                     compensations.set(bucket, kahanSummation.delta());

+ 44 - 18
x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/RateAggregationBuilder.java

@@ -5,6 +5,11 @@
  */
 package org.elasticsearch.xpack.analytics.rate;
 
+import java.io.IOException;
+import java.util.Map;
+import java.util.Objects;
+
+import org.elasticsearch.Version;
 import org.elasticsearch.common.ParseField;
 import org.elasticsearch.common.Rounding;
 import org.elasticsearch.common.io.stream.StreamInput;
@@ -24,13 +29,10 @@ import org.elasticsearch.search.aggregations.support.ValuesSourceConfig;
 import org.elasticsearch.search.aggregations.support.ValuesSourceRegistry;
 import org.elasticsearch.search.aggregations.support.ValuesSourceType;
 
-import java.io.IOException;
-import java.util.Map;
-import java.util.Objects;
-
 public class RateAggregationBuilder extends ValuesSourceAggregationBuilder.LeafOnly<ValuesSource, RateAggregationBuilder> {
     public static final String NAME = "rate";
     public static final ParseField UNIT_FIELD = new ParseField("unit");
+    public static final ParseField MODE_FIELD = new ParseField("mode");
     public static final ValuesSourceRegistry.RegistryKey<RateAggregatorSupplier> REGISTRY_KEY = new ValuesSourceRegistry.RegistryKey<>(
         NAME,
         RateAggregatorSupplier.class
@@ -40,9 +42,11 @@ public class RateAggregationBuilder extends ValuesSourceAggregationBuilder.LeafO
     static {
         ValuesSourceAggregationBuilder.declareFields(PARSER, true, true, false, false);
         PARSER.declareString(RateAggregationBuilder::rateUnit, UNIT_FIELD);
+        PARSER.declareString(RateAggregationBuilder::rateMode, MODE_FIELD);
     }
 
     Rounding.DateTimeUnit rateUnit;
+    RateMode rateMode;
 
     public static void registerAggregators(ValuesSourceRegistry.Builder builder) {
         RateAggregatorFactory.registerAggregators(builder);
@@ -58,6 +62,8 @@ public class RateAggregationBuilder extends ValuesSourceAggregationBuilder.LeafO
         Map<String, Object> metadata
     ) {
         super(clone, factoriesBuilder, metadata);
+        this.rateUnit = clone.rateUnit;
+        this.rateMode = clone.rateMode;
     }
 
     @Override
@@ -76,6 +82,11 @@ public class RateAggregationBuilder extends ValuesSourceAggregationBuilder.LeafO
         } else {
             rateUnit = null;
         }
+        if (in.getVersion().onOrAfter(Version.V_8_0_0)) {
+            if (in.readBoolean()) {
+                rateMode = in.readEnum(RateMode.class);
+            }
+        }
     }
 
     @Override
@@ -90,6 +101,14 @@ public class RateAggregationBuilder extends ValuesSourceAggregationBuilder.LeafO
         } else {
             out.writeByte((byte) 0);
         }
+        if (out.getVersion().onOrAfter(Version.V_8_0_0)) {
+            if (rateMode != null) {
+                out.writeBoolean(true);
+                out.writeEnum(rateMode);
+            } else {
+                out.writeBoolean(false);
+            }
+        }
     }
 
     @Override
@@ -104,7 +123,12 @@ public class RateAggregationBuilder extends ValuesSourceAggregationBuilder.LeafO
         AggregatorFactory parent,
         AggregatorFactories.Builder subFactoriesBuilder
     ) throws IOException {
-        return new RateAggregatorFactory(name, config, rateUnit, context, parent, subFactoriesBuilder, metadata);
+        if (field() == null && script() == null) {
+            if (rateMode != null) {
+                throw new IllegalArgumentException("The mode parameter is only supported with field or script");
+            }
+        }
+        return new RateAggregatorFactory(name, config, rateUnit, rateMode, context, parent, subFactoriesBuilder, metadata);
     }
 
     @Override
@@ -112,6 +136,9 @@ public class RateAggregationBuilder extends ValuesSourceAggregationBuilder.LeafO
         if (rateUnit != null) {
             builder.field(UNIT_FIELD.getPreferredName(), rateUnit.shortName());
         }
+        if (rateMode != null) {
+            builder.field(MODE_FIELD.getPreferredName(), rateMode.value());
+        }
         return builder;
     }
 
@@ -129,6 +156,15 @@ public class RateAggregationBuilder extends ValuesSourceAggregationBuilder.LeafO
         return this;
     }
 
+    public RateAggregationBuilder rateMode(String rateMode) {
+        return rateMode(RateMode.resolve(rateMode));
+    }
+
+    public RateAggregationBuilder rateMode(RateMode rateMode) {
+        this.rateMode = rateMode;
+        return this;
+    }
+
     static Rounding.DateTimeUnit parse(String rateUnit) {
         Rounding.DateTimeUnit parsedRate = DateHistogramAggregationBuilder.DATE_FIELD_UNITS.get(rateUnit);
         if (parsedRate == null) {
@@ -140,17 +176,7 @@ public class RateAggregationBuilder extends ValuesSourceAggregationBuilder.LeafO
     @Override
     protected ValuesSourceConfig resolveConfig(AggregationContext context) {
         if (field() == null && script() == null) {
-            return new ValuesSourceConfig(
-                CoreValuesSourceType.NUMERIC,
-                null,
-                true,
-                null,
-                null,
-                1.0,
-                null,
-                DocValueFormat.RAW,
-                context
-            );
+            return new ValuesSourceConfig(CoreValuesSourceType.NUMERIC, null, true, null, null, 1.0, null, DocValueFormat.RAW, context);
         } else {
             return super.resolveConfig(context);
         }
@@ -162,11 +188,11 @@ public class RateAggregationBuilder extends ValuesSourceAggregationBuilder.LeafO
         if (o == null || getClass() != o.getClass()) return false;
         if (!super.equals(o)) return false;
         RateAggregationBuilder that = (RateAggregationBuilder) o;
-        return rateUnit == that.rateUnit;
+        return rateUnit == that.rateUnit && rateMode == that.rateMode;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(super.hashCode(), rateUnit);
+        return Objects.hash(super.hashCode(), rateUnit, rateMode);
     }
 }

+ 6 - 2
x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/RateAggregatorFactory.java

@@ -29,10 +29,13 @@ class RateAggregatorFactory extends ValuesSourceAggregatorFactory {
 
     private final Rounding.DateTimeUnit rateUnit;
 
+    private final RateMode rateMode;
+
     RateAggregatorFactory(
         String name,
         ValuesSourceConfig config,
         Rounding.DateTimeUnit rateUnit,
+        RateMode rateMode,
         AggregationContext context,
         AggregatorFactory parent,
         AggregatorFactories.Builder subFactoriesBuilder,
@@ -40,6 +43,7 @@ class RateAggregatorFactory extends ValuesSourceAggregatorFactory {
     ) throws IOException {
         super(name, config, context, parent, subFactoriesBuilder, metadata);
         this.rateUnit = rateUnit;
+        this.rateMode = rateMode;
     }
 
     static void registerAggregators(ValuesSourceRegistry.Builder builder) {
@@ -59,7 +63,7 @@ class RateAggregatorFactory extends ValuesSourceAggregatorFactory {
 
     @Override
     protected Aggregator createUnmapped(SearchContext searchContext, Aggregator parent, Map<String, Object> metadata) throws IOException {
-        return new AbstractRateAggregator(name, config, rateUnit, searchContext, parent, metadata) {
+        return new AbstractRateAggregator(name, config, rateUnit, rateMode, searchContext, parent, metadata) {
             @Override
             public LeafBucketCollector getLeafCollector(LeafReaderContext ctx, LeafBucketCollector sub) {
                 return LeafBucketCollector.NO_OP_COLLECTOR;
@@ -76,6 +80,6 @@ class RateAggregatorFactory extends ValuesSourceAggregatorFactory {
     ) throws IOException {
         return context.getValuesSourceRegistry()
             .getAggregator(RateAggregationBuilder.REGISTRY_KEY, config)
-            .build(name, config, rateUnit, searchContext, parent, metadata);
+            .build(name, config, rateUnit, rateMode, searchContext, parent, metadata);
     }
 }

+ 1 - 0
x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/RateAggregatorSupplier.java

@@ -19,6 +19,7 @@ public interface RateAggregatorSupplier {
         String name,
         ValuesSourceConfig valuesSourceConfig,
         Rounding.DateTimeUnit rateUnit,
+        RateMode rateMode,
         SearchContext context,
         Aggregator parent,
         Map<String, Object> metadata

+ 24 - 0
x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/RateMode.java

@@ -0,0 +1,24 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+package org.elasticsearch.xpack.analytics.rate;
+
+import java.util.Locale;
+
+/**
+ * Rate mode - value_count or sum
+ */
+public enum RateMode {
+    VALUE_COUNT, SUM;
+
+    public static RateMode resolve(String name) {
+        return RateMode.valueOf(name.toUpperCase(Locale.ROOT));
+    }
+
+    public String value() {
+        return name().toLowerCase(Locale.ROOT);
+    }
+
+}

+ 3 - 0
x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/rate/RateAggregationBuilderTests.java

@@ -56,6 +56,9 @@ public class RateAggregationBuilderTests extends AbstractSerializingTestCase<Rat
             } else {
                 aggregationBuilder.script(new Script(randomAlphaOfLength(10)));
             }
+            if (randomBoolean()) {
+                aggregationBuilder.rateMode(randomFrom(RateMode.values()));
+            }
         }
         if (randomBoolean()) {
             aggregationBuilder.rateUnit(randomFrom(Rounding.DateTimeUnit.values()));

+ 102 - 0
x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/rate/RateAggregatorTests.java

@@ -142,6 +142,31 @@ public class RateAggregatorTests extends AggregatorTestCase {
         });
     }
 
+    public void testDocValuesMonthToMonthValueCount() throws IOException {
+        MappedFieldType dateType = dateFieldType(DATE_FIELD);
+        MappedFieldType numType = new NumberFieldMapper.NumberFieldType("val", NumberFieldMapper.NumberType.INTEGER);
+        RateAggregationBuilder rateAggregationBuilder = new RateAggregationBuilder("my_rate").rateUnit("month")
+            .field("val")
+            .rateMode("value_count");
+
+        DateHistogramAggregationBuilder dateHistogramAggregationBuilder = new DateHistogramAggregationBuilder("my_date");
+        dateHistogramAggregationBuilder.field(DATE_FIELD);
+        dateHistogramAggregationBuilder.calendarInterval(new DateHistogramInterval("month"));
+
+        dateHistogramAggregationBuilder.subAggregation(rateAggregationBuilder);
+        testCase(dateHistogramAggregationBuilder, new MatchAllDocsQuery(), iw -> {
+            iw.addDocument(doc("2010-03-12T01:07:45", new SortedNumericDocValuesField("val", 1)));
+            iw.addDocument(doc("2010-04-01T03:43:34", new SortedNumericDocValuesField("val", 3)));
+            iw.addDocument(
+                doc("2010-04-27T03:43:34", new SortedNumericDocValuesField("val", 4), new SortedNumericDocValuesField("val", 5))
+            );
+        }, (Consumer<InternalDateHistogram>) dh -> {
+            assertThat(dh.getBuckets(), hasSize(2));
+            assertThat(((InternalRate) dh.getBuckets().get(0).getAggregations().asList().get(0)).value(), closeTo(1.0, 0.000001));
+            assertThat(((InternalRate) dh.getBuckets().get(1).getAggregations().asList().get(0)).value(), closeTo(3.0, 0.000001));
+        }, dateType, numType);
+    }
+
     public void testDocValuesMonthToMonthDefaultRate() throws IOException {
         testCase(new MatchAllDocsQuery(), "month", true, null, "val", iw -> {
             iw.addDocument(doc("2010-03-12T01:07:45", new NumericDocValuesField("val", 1)));
@@ -260,6 +285,9 @@ public class RateAggregatorTests extends AggregatorTestCase {
         MappedFieldType numType = new NumberFieldMapper.NumberFieldType("val", NumberFieldMapper.NumberType.INTEGER);
         MappedFieldType dateType = dateFieldType(DATE_FIELD);
         RateAggregationBuilder rateAggregationBuilder = new RateAggregationBuilder("my_rate").rateUnit("month").field("val");
+        if (randomBoolean()) {
+            rateAggregationBuilder.rateMode("sum");
+        }
         DateHistogramAggregationBuilder dateHistogramAggregationBuilder = new DateHistogramAggregationBuilder("my_date").field(DATE_FIELD)
             .calendarInterval(new DateHistogramInterval("month"))
             .subAggregation(rateAggregationBuilder);
@@ -291,6 +319,9 @@ public class RateAggregatorTests extends AggregatorTestCase {
         MappedFieldType dateType = dateFieldType(DATE_FIELD);
         MappedFieldType keywordType = new KeywordFieldMapper.KeywordFieldType("term");
         RateAggregationBuilder rateAggregationBuilder = new RateAggregationBuilder("my_rate").rateUnit("month").field("val");
+        if (randomBoolean()) {
+            rateAggregationBuilder.rateMode("sum");
+        }
         TermsAggregationBuilder termsAggregationBuilder = new TermsAggregationBuilder("my_term").field("term")
             .subAggregation(rateAggregationBuilder);
         DateHistogramAggregationBuilder dateHistogramAggregationBuilder = new DateHistogramAggregationBuilder("my_date").field(DATE_FIELD)
@@ -348,6 +379,9 @@ public class RateAggregatorTests extends AggregatorTestCase {
         MappedFieldType dateType = dateFieldType(DATE_FIELD);
         MappedFieldType keywordType = new KeywordFieldMapper.KeywordFieldType("term");
         RateAggregationBuilder rateAggregationBuilder = new RateAggregationBuilder("my_rate").rateUnit("month").field("val");
+        if (randomBoolean()) {
+            rateAggregationBuilder.rateMode("sum");
+        }
 
         DateHistogramAggregationBuilder dateHistogramAggregationBuilder = new DateHistogramAggregationBuilder("my_date").field(DATE_FIELD)
             .calendarInterval(new DateHistogramInterval("month"))
@@ -371,6 +405,9 @@ public class RateAggregatorTests extends AggregatorTestCase {
         RateAggregationBuilder rateAggregationBuilder = new RateAggregationBuilder("my_rate").rateUnit("month")
             .field("val")
             .format("00.0/M");
+        if (randomBoolean()) {
+            rateAggregationBuilder.rateMode("sum");
+        }
 
         DateHistogramAggregationBuilder dateHistogramAggregationBuilder = new DateHistogramAggregationBuilder("my_date").field(DATE_FIELD)
             .calendarInterval(new DateHistogramInterval("month"))
@@ -392,6 +429,9 @@ public class RateAggregatorTests extends AggregatorTestCase {
         MappedFieldType histType = new HistogramFieldMapper.HistogramFieldType("val", Collections.emptyMap());
         MappedFieldType dateType = dateFieldType(DATE_FIELD);
         RateAggregationBuilder rateAggregationBuilder = new RateAggregationBuilder("my_rate").rateUnit("month").field("val");
+        if (randomBoolean()) {
+            rateAggregationBuilder.rateMode("sum");
+        }
 
         DateHistogramAggregationBuilder dateHistogramAggregationBuilder = new DateHistogramAggregationBuilder("my_date").field(DATE_FIELD)
             .calendarInterval(new DateHistogramInterval("month"))
@@ -411,6 +451,9 @@ public class RateAggregatorTests extends AggregatorTestCase {
         MappedFieldType histType = new HistogramFieldMapper.HistogramFieldType("val", Collections.emptyMap());
         MappedFieldType dateType = dateFieldType(DATE_FIELD);
         RateAggregationBuilder rateAggregationBuilder = new RateAggregationBuilder("my_rate").rateUnit("month").field("val");
+        if (randomBoolean()) {
+            rateAggregationBuilder.rateMode("sum");
+        }
 
         DateHistogramAggregationBuilder dateHistogramAggregationBuilder = new DateHistogramAggregationBuilder("my_date").field(DATE_FIELD)
             .calendarInterval(new DateHistogramInterval("year"))
@@ -425,6 +468,47 @@ public class RateAggregatorTests extends AggregatorTestCase {
         }, dateType, histType);
     }
 
+    public void testHistogramFieldMonthToMonthValueCount() throws IOException {
+        MappedFieldType histType = new HistogramFieldMapper.HistogramFieldType("val", Collections.emptyMap());
+        MappedFieldType dateType = dateFieldType(DATE_FIELD);
+        RateAggregationBuilder rateAggregationBuilder = new RateAggregationBuilder("my_rate").rateUnit("month")
+            .rateMode("value_count")
+            .field("val");
+
+        DateHistogramAggregationBuilder dateHistogramAggregationBuilder = new DateHistogramAggregationBuilder("my_date").field(DATE_FIELD)
+            .calendarInterval(new DateHistogramInterval("month"))
+            .subAggregation(rateAggregationBuilder);
+
+        testCase(dateHistogramAggregationBuilder, new MatchAllDocsQuery(), iw -> {
+            iw.addDocument(doc("2010-03-01T00:00:00", histogramFieldDocValues("val", new double[] { 1, 2 })));
+            iw.addDocument(doc("2010-04-01T00:00:00", histogramFieldDocValues("val", new double[] { 3, 4, 5 })));
+        }, (Consumer<InternalDateHistogram>) dh -> {
+            assertThat(dh.getBuckets(), hasSize(2));
+            assertThat(((InternalRate) dh.getBuckets().get(0).getAggregations().asList().get(0)).getValue(), closeTo(2.0, 0.000001));
+            assertThat(((InternalRate) dh.getBuckets().get(1).getAggregations().asList().get(0)).getValue(), closeTo(3.0, 0.000001));
+        }, dateType, histType);
+    }
+
+    public void testHistogramFieldMonthToYearValueCount() throws IOException {
+        MappedFieldType histType = new HistogramFieldMapper.HistogramFieldType("val", Collections.emptyMap());
+        MappedFieldType dateType = dateFieldType(DATE_FIELD);
+        RateAggregationBuilder rateAggregationBuilder = new RateAggregationBuilder("my_rate").rateUnit("month")
+            .rateMode("value_count")
+            .field("val");
+
+        DateHistogramAggregationBuilder dateHistogramAggregationBuilder = new DateHistogramAggregationBuilder("my_date").field(DATE_FIELD)
+            .calendarInterval(new DateHistogramInterval("year"))
+            .subAggregation(rateAggregationBuilder);
+
+        testCase(dateHistogramAggregationBuilder, new MatchAllDocsQuery(), iw -> {
+            iw.addDocument(doc("2010-03-01T00:00:00", histogramFieldDocValues("val", new double[] { 1, 2 })));
+            iw.addDocument(doc("2010-04-01T00:00:00", histogramFieldDocValues("val", new double[] { 3, 4, 5 })));
+        }, (Consumer<InternalDateHistogram>) dh -> {
+            assertThat(dh.getBuckets(), hasSize(1));
+            assertThat(((InternalRate) dh.getBuckets().get(0).getAggregations().asList().get(0)).getValue(), closeTo(5.0 / 12, 0.000001));
+        }, dateType, histType);
+    }
+
     public void testFilterWithHistogramField() throws IOException {
         MappedFieldType histType = new HistogramFieldMapper.HistogramFieldType("val", Collections.emptyMap());
         MappedFieldType dateType = dateFieldType(DATE_FIELD);
@@ -449,6 +533,24 @@ public class RateAggregatorTests extends AggregatorTestCase {
         }, dateType, histType, keywordType);
     }
 
+    public void testModeWithoutField() {
+        MappedFieldType dateType = dateFieldType(DATE_FIELD);
+        MappedFieldType numType = new NumberFieldMapper.NumberFieldType("val", NumberFieldMapper.NumberType.INTEGER);
+        RateAggregationBuilder rateAggregationBuilder = new RateAggregationBuilder("my_rate").rateUnit("month").rateMode("sum");
+
+        DateHistogramAggregationBuilder dateHistogramAggregationBuilder = new DateHistogramAggregationBuilder("my_date").field(DATE_FIELD)
+            .calendarInterval(new DateHistogramInterval("month"))
+            .subAggregation(rateAggregationBuilder);
+
+        IllegalArgumentException ex = expectThrows(
+            IllegalArgumentException.class,
+            () -> testCase(dateHistogramAggregationBuilder, new MatchAllDocsQuery(), iw -> {
+                iw.addDocument(doc("2010-03-12T01:07:45", new SortedNumericDocValuesField("val", 1)));
+            }, h -> { fail("Shouldn't be here"); }, dateType, numType)
+        );
+        assertEquals("The mode parameter is only supported with field or script", ex.getMessage());
+    }
+
     private void testCase(
         Query query,
         String interval,