Type casting in Groovy reduce script of scripted metric aggregation

I'm using the output of a scripted metric aggregation like the one I describe here as the payload for a Watcher condition. I'd expect the ratio of two numbers to also be a number, but it's coming back as a string unless I explicitly cast it as a float like this:

"reduce_script": "clicks = searches = 0; for (agg in _aggs) {  clicks += agg.click ; searches += agg.search ;}; ctr_str = searches == 0 ? 0 : 100 * clicks / searches; return ctr_str.toFloat();"

or even

"reduce_script": "clicks = searches = 0; for (agg in _aggs) {  clicks += agg.click ; searches += agg.search ;}; float ctr_str = searches == 0 ? 0 : 100 * clicks / searches; ctr_str;"

I'm not sure why I have to do this explicit casting though. Can anyone help this Groovy newbie out?

The dynamic nature of things can often burn you as often as it can help you in Groovy, as it seems to be doing here. The core problem here is that Groovy does division with integers using BigDecimal, which ends up being a string when it's time to serialize it. You can prove this by using script fields to see what is happening (note: with script.inline: on):

PUT /test/type/1
{
  "field1" : 123,
  "field2" : 123
}

GET /test/_search
{
  "script_fields": {
    "FIELD": {
      "script": "(doc['field1'].value / doc['field2'].value).class"
    }
  }
}

This returns:

{
   "took": 64,
   "timed_out": false,
   "_shards": {
      "total": 5,
      "successful": 5,
      "failed": 0
   },
   "hits": {
      "total": 1,
      "max_score": 1,
      "hits": [
         {
            "_index": "test",
            "_type": "type",
            "_id": "1",
            "_score": 1,
            "fields": {
               "FIELD": [
                  "class java.math.BigDecimal"
               ]
            }
         }
      ]
   }
}

Your best bet is to cast the values to tip off Groovy what it needs to become before returning it. It's effectively what you're doing with toFloat();.

Another solution exists without casting, as you are using a constant of 100 in your calculation: change it to 100.0, which will make it a double rather than an integer, and the result will automatically be a double as a result.

1 Like