[WIP][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).

The design doc can be found at [SPARK-3174](https://issues.apache.org/jira/browse/SPARK-3174) (under "Heuristics for Scaling Executors". This deviates from the design in two ways: (1) the remove policy is significantly simplified by cutting out the exponential increase logic that mirrors the add policy, and (2) the configuration of the add policy is split up into `addExecutorThreshold` and `addExecutorInterval`. The main reason for this is that the user may want to configure how quickly executors are added, but want to keep the trigger condition the same.

Comments and feedback are most welcome (but please keep in mind that this is still WIP!).
A lot of the logic in here seems related to retries. Why do we have to deal with retrying at all? What if we just do individual executor requests one at a time and we don't do any dynamic scaling until we get back the result of the last request? Could this simplify a lot of complexity here? My understanding is that YARN etc should be able to launch executors in order seconds, so we could add ten or more executors per minute... that seems pretty reasonable for the type of workloads this is targeting.