[SPARK-3795] Heuristics for dynamically scaling executors

This is part of a bigger effort to provide elastic scaling of executors within a Spark application ([SPARK-3174](https://issues.apache.org/jira/browse/SPARK-3174)). This PR does not provide any functionality by itself; it is a skeleton that is missing a mechanism to be added later in [SPARK-3822](https://issues.apache.org/jira/browse/SPARK-3822).

Comments and feedback are most welcome. For those of you reviewing this in detail, I highly recommend doing it through your favorite IDE instead of through the diff here.
Ok, I was worried about overflow but I just did some math to find that this is basically impossible:
```
> System.nanoTime
res0: Long = 1414606728388783000
> Math.log(Long.MaxValue - System.nanoTime) / Math.log(2)
res1: Double = 62.75980010864823 // we have 2^62+ nano seconds to spare
> Math.pow(2, 62) / Math.pow(10, 9) / 86400 / 365
res2: Double = 146.235604338768
```
This means the application must run for 146+ years before this could happen. I think we can safely assume this won't be the case. :)