Skip to content

Commit 3e43512

Browse files
authored
On-Demand and Spot Price Retrieval Filters w/ a Cache (#73)
* Fetch pricing for ondemand instances and spot w/ cache implementation * remove unnecessary caps on flag docs and clarified sum var name in ec2pricing
1 parent fd56d04 commit 3e43512

File tree

19 files changed

+3081
-61
lines changed

19 files changed

+3081
-61
lines changed

README.md

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -79,50 +79,64 @@ $ export AWS_REGION="us-east-1"
7979
```
8080
$ ec2-instance-selector --memory 4 --vcpus 2 --cpu-architecture x86_64 -r us-east-1
8181
c5.large
82+
c5a.large
83+
c5ad.large
8284
c5d.large
8385
t2.medium
8486
t3.medium
8587
t3a.medium
8688
```
8789

88-
**Find instance types that support 100GB/s networking**
90+
**Find instance types that support 100GB/s networking that can be purchased as spot instances**
8991
```
90-
$ ec2-instance-selector --network-performance 100 -r us-east-1
92+
$ ec2-instance-selector --network-performance 100 --usage-class spot -r us-east-1
9193
c5n.18xlarge
9294
c5n.metal
95+
c6gn.16xlarge
96+
g4dn.metal
9397
i3en.24xlarge
9498
i3en.metal
9599
inf1.24xlarge
96100
m5dn.24xlarge
101+
m5dn.metal
97102
m5n.24xlarge
103+
m5n.metal
104+
m5zn.12xlarge
105+
m5zn.metal
98106
p3dn.24xlarge
107+
p4d.24xlarge
99108
r5dn.24xlarge
109+
r5dn.metal
100110
r5n.24xlarge
111+
r5n.metal
101112
```
102113

103114
**Short Table Output**
104115
```
105116
$ ec2-instance-selector --memory 4 --vcpus 2 --cpu-architecture x86_64 -r us-east-1 -o table
106117
Instance Type VCPUs Mem (GiB)
107118
------------- ----- ---------
108-
c5.large 2 4.000
109-
c5d.large 2 4.000
110-
t2.medium 2 4.000
111-
t3.medium 2 4.000
112-
t3a.medium 2 4.000
119+
c5.large 2 4
120+
c5a.large 2 4
121+
c5ad.large 2 4
122+
c5d.large 2 4
123+
t2.medium 2 4
124+
t3.medium 2 4
125+
t3a.medium 2 4
113126
```
114127

115128
**Wide Table Output**
116129
```
117130
$ ec2-instance-selector --memory 4 --vcpus 2 --cpu-architecture x86_64 -r us-east-1 -o table-wide
118-
Instance Type VCPUs Mem (GiB) Hypervisor Current Gen Hibernation Support CPU Arch Network Performance ENIs GPUs
119-
------------- ----- --------- ---------- ----------- ------------------- -------- ------------------- ---- ----
120-
c5.large 2 4.000 nitro true true x86_64 Up to 10 Gigabit 3 0
121-
c5a.large 2 4.000 nitro true false x86_64 Up to 10 Gigabit 3 0
122-
c5d.large 2 4.000 nitro true false x86_64 Up to 10 Gigabit 3 0
123-
t2.medium 2 4.000 xen true true i386, x86_64 Low to Moderate 3 0
124-
t3.medium 2 4.000 nitro true false x86_64 Up to 5 Gigabit 3 0
125-
t3a.medium 2 4.000 nitro true false x86_64 Up to 5 Gigabit 3 0
131+
Instance Type VCPUs Mem (GiB) Hypervisor Current Gen Hibernation Support CPU Arch Network Performance ENIs GPUs GPU Mem (GiB) GPU Info On-Demand Price/Hr
132+
------------- ----- --------- ---------- ----------- ------------------- -------- ------------------- ---- ---- ------------- -------- ------------------
133+
c5.large 2 4 nitro true true x86_64 Up to 10 Gigabit 3 0 0 -No Price Filter Specified-
134+
c5a.large 2 4 nitro true false x86_64 Up to 10 Gigabit 3 0 0 -No Price Filter Specified-
135+
c5ad.large 2 4 nitro true false x86_64 Up to 10 Gigabit 3 0 0 -No Price Filter Specified-
136+
c5d.large 2 4 nitro true false x86_64 Up to 10 Gigabit 3 0 0 -No Price Filter Specified-
137+
t2.medium 2 4 xen true true i386, x86_64 Low to Moderate 3 0 0 -No Price Filter Specified-
138+
t3.medium 2 4 nitro true true x86_64 Up to 5 Gigabit 3 0 0 -No Price Filter Specified-
139+
t3a.medium 2 4 nitro true true x86_64 Up to 5 Gigabit 3 0 0 -No Price Filter Specified-
126140
```
127141

128142
**All CLI Options**
@@ -171,23 +185,28 @@ Filter Flags:
171185
--network-performance-max int Maximum Bandwidth in Gib/s of network performance (Example: 100) If --network-performance-min is not specified, the lower bound will be 0
172186
--network-performance-min int Minimum Bandwidth in Gib/s of network performance (Example: 100) If --network-performance-max is not specified, the upper bound will be infinity
173187
--placement-group-strategy string Placement group strategy: [cluster, partition, spread]
188+
--price-per-hour float Price/hour in USD (Example: 0.09) (sets --price-per-hour-min and -max to the same value)
189+
--price-per-hour-max float Maximum Price/hour in USD (Example: 0.09) If --price-per-hour-min is not specified, the lower bound will be 0
190+
--price-per-hour-min float Minimum Price/hour in USD (Example: 0.09) If --price-per-hour-max is not specified, the upper bound will be infinity
174191
--root-device-type string Supported root device types: [ebs or instance-store]
175192
-u, --usage-class string Usage class: [spot or on-demand]
176193
-c, --vcpus int Number of vcpus available to the instance type. (sets --vcpus-min and -max to the same value)
177194
--vcpus-max int Maximum Number of vcpus available to the instance type. If --vcpus-min is not specified, the lower bound will be 0
178195
--vcpus-min int Minimum Number of vcpus available to the instance type. If --vcpus-max is not specified, the upper bound will be infinity
179196
--vcpus-to-memory-ratio string The ratio of vcpus to GiBs of memory. (Example: 1:2)
197+
--virtualization-type string Virtualization Type supported: [hvm or pv]
180198
181199
182200
Suite Flags:
183201
--base-instance-type string Instance Type used to retrieve similarly spec'd instance types
184202
--flexible Retrieves a group of instance types spanning multiple generations based on opinionated defaults and user overridden resource filters
203+
--service string Filter instance types based on service support (Example: eks, eks-20201211, or emr-5.20.0)
185204
186205
187206
Global Flags:
188207
-h, --help Help
189208
--max-results int The maximum number of instance types that match your criteria to return (default 20)
190-
-o, --output string Specify the output format (table, table-wide)
209+
-o, --output string Specify the output format (table, table-wide, one-line)
191210
--profile string AWS CLI profile to use for credentials and config
192211
-r, --region string AWS Region to use for API requests (NOTE: if not passed in, uses AWS SDK default precedence)
193212
-v, --verbose Verbose - will print out full instance specs
@@ -267,7 +286,7 @@ func main() {
267286
$ git clone https://github.com/aws/amazon-ec2-instance-selector.git
268287
$ cd amazon-ec2-instance-selector/
269288
$ go run cmd/examples/example1.go
270-
[c1.medium c3.large c4.large c5.large c5d.large t2.medium t3.medium t3.micro t3.small t3a.medium t3a.micro t3a.small]
289+
[c4.large c5.large c5a.large c5d.large t2.medium t3.medium t3.small t3a.medium t3a.small]
271290
```
272291

273292
## Building

cmd/main.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ const (
6969
allowList = "allow-list"
7070
denyList = "deny-list"
7171
virtualizationType = "virtualization-type"
72+
pricePerHour = "price-per-hour"
7273
)
7374

7475
// Aggregate Filter Flags
@@ -142,6 +143,7 @@ Full docs can be found at github.com/aws/amazon-` + binName
142143
cli.RegexFlag(allowList, nil, nil, "List of allowed instance types to select from w/ regex syntax (Example: m[3-5]\\.*)")
143144
cli.RegexFlag(denyList, nil, nil, "List of instance types which should be excluded w/ regex syntax (Example: m[1-2]\\.*)")
144145
cli.StringOptionsFlag(virtualizationType, nil, nil, "Virtualization Type supported: [hvm or pv]", []string{"hvm", "paravirtual", "pv"})
146+
cli.Float64MinMaxRangeFlags(pricePerHour, nil, nil, "Price/hour in USD (Example: 0.09)")
145147

146148
// Suite Flags - higher level aggregate filters that return opinionated result
147149

@@ -183,6 +185,13 @@ Full docs can be found at github.com/aws/amazon-` + binName
183185
flags[region] = sess.Config.Region
184186

185187
instanceSelector := selector.New(sess)
188+
if _, ok := flags[pricePerHour]; ok {
189+
if flags[usageClass] == nil || *flags[usageClass].(*string) == "on-demand" {
190+
instanceSelector.EC2Pricing.HydrateOndemandCache()
191+
} else {
192+
instanceSelector.EC2Pricing.HydrateSpotCache(30)
193+
}
194+
}
186195

187196
filters := selector.Filters{
188197
VCpusRange: cli.IntRangeMe(flags[vcpus]),
@@ -212,6 +221,7 @@ Full docs can be found at github.com/aws/amazon-` + binName
212221
Flexible: cli.BoolMe(flags[flexible]),
213222
Service: cli.StringMe(flags[service]),
214223
VirtualizationType: cli.StringMe(flags[virtualizationType]),
224+
PricePerHour: cli.Float64RangeMe(flags[pricePerHour]),
215225
}
216226

217227
if flags[verbose] != nil {

pkg/cli/cli.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package cli
1616

1717
import (
1818
"fmt"
19+
"math"
1920
"os"
2021
"reflect"
2122
"strings"
@@ -185,6 +186,10 @@ func (cl *CommandLineInterface) SetUntouchedFlagValuesToNil() error {
185186
if v.Quantity == 0 {
186187
cl.Flags[f.Name] = nil
187188
}
189+
case *float64:
190+
if reflect.ValueOf(*v).IsZero() {
191+
cl.Flags[f.Name] = nil
192+
}
188193
case *string:
189194
if reflect.ValueOf(*v).IsZero() {
190195
cl.Flags[f.Name] = nil
@@ -231,6 +236,8 @@ func (cl *CommandLineInterface) ProcessRangeFilterFlags() error {
231236
cl.Flags[rangeHelperMin] = cl.IntMe(0)
232237
case *bytequantity.ByteQuantity:
233238
cl.Flags[rangeHelperMin] = cl.ByteQuantityMe(bytequantity.ByteQuantity{Quantity: 0})
239+
case *float64:
240+
cl.Flags[rangeHelperMin] = cl.Float64Me(0.0)
234241
default:
235242
return fmt.Errorf("Unable to set %s", rangeHelperMax)
236243
}
@@ -240,6 +247,8 @@ func (cl *CommandLineInterface) ProcessRangeFilterFlags() error {
240247
cl.Flags[rangeHelperMax] = cl.IntMe(maxInt)
241248
case *bytequantity.ByteQuantity:
242249
cl.Flags[rangeHelperMax] = cl.ByteQuantityMe(bytequantity.ByteQuantity{Quantity: maxUint64})
250+
case *float64:
251+
cl.Flags[rangeHelperMax] = cl.Float64Me(math.MaxFloat64)
243252
default:
244253
return fmt.Errorf("Unable to set %s", rangeHelperMin)
245254
}
@@ -256,6 +265,11 @@ func (cl *CommandLineInterface) ProcessRangeFilterFlags() error {
256265
LowerBound: *cl.ByteQuantityMe(cl.Flags[rangeHelperMin]),
257266
UpperBound: *cl.ByteQuantityMe(cl.Flags[rangeHelperMax]),
258267
}
268+
case *float64:
269+
cl.Flags[flagName] = &selector.Float64RangeFilter{
270+
LowerBound: *cl.Float64Me(cl.Flags[rangeHelperMin]),
271+
UpperBound: *cl.Float64Me(cl.Flags[rangeHelperMax]),
272+
}
259273
}
260274
}
261275
return nil

pkg/cli/cli_test.go

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ func TestParseFlags_IntRange(t *testing.T) {
159159
h.Ok(t, err)
160160
flagMinOutput = flags[flagMinArg].(*int)
161161
flagMaxOutput = flags[flagMaxArg].(*int)
162-
h.Assert(t, *flagMinOutput == 10 && *flagMaxOutput == 500, "Flag %s max should have been parsed from cmdline and min set to 0", flagArg)
162+
h.Assert(t, *flagMinOutput == 10 && *flagMaxOutput == 500, "Flag %s min and max should have been parsed from cmdline", flagArg)
163163
}
164164

165165
func TestParseFlags_IntRangeErr(t *testing.T) {
@@ -222,6 +222,53 @@ func TestParseFlags_ByteQuantityRange(t *testing.T) {
222222
h.Assert(t, flagType == bqRangeFilterType, "%s should be of type %v, instead got %v", flagArg, bqRangeFilterType, flagType)
223223
}
224224

225+
func TestParseFlags_Float64Range(t *testing.T) {
226+
flagName := "test-flag"
227+
flagMinArg := fmt.Sprintf("%s-%s", flagName, "min")
228+
flagMaxArg := fmt.Sprintf("%s-%s", flagName, "max")
229+
flagArg := fmt.Sprintf("--%s", flagName)
230+
231+
// Root set Min and Max to the same val
232+
cli := getTestCLI()
233+
cli.Float64MinMaxRangeFlags(flagName, nil, nil, "Test")
234+
os.Args = []string{"ec2-instance-selector", flagArg, "5.1"}
235+
flags, err := cli.ParseFlags()
236+
h.Ok(t, err)
237+
flagMinOutput := flags[flagMinArg].(*float64)
238+
flagMaxOutput := flags[flagMaxArg].(*float64)
239+
h.Assert(t, *flagMinOutput == 5.1 && *flagMaxOutput == 5.1, "Flag %s min and max should have been parsed to the same number", flagArg)
240+
241+
// Min is set to a val and max is set to maxInt
242+
cli = getTestCLI()
243+
cli.Float64MinMaxRangeFlags(flagName, nil, nil, "Test")
244+
os.Args = []string{"ec2-instance-selector", "--" + flagMinArg, "5.1"}
245+
flags, err = cli.ParseFlags()
246+
h.Ok(t, err)
247+
flagMinOutput = flags[flagMinArg].(*float64)
248+
flagMaxOutput = flags[flagMaxArg].(*float64)
249+
h.Assert(t, *flagMinOutput == 5.1 && *flagMaxOutput == math.MaxFloat64, "Flag %s min should have been parsed from cmdline and max set to math.MaxFloat64", flagArg)
250+
251+
// Max is set to a val and min is set to 0
252+
cli = getTestCLI()
253+
cli.Float64MinMaxRangeFlags(flagName, nil, nil, "Test")
254+
os.Args = []string{"ec2-instance-selector", "--" + flagMaxArg, "5.1"}
255+
flags, err = cli.ParseFlags()
256+
h.Ok(t, err)
257+
flagMinOutput = flags[flagMinArg].(*float64)
258+
flagMaxOutput = flags[flagMaxArg].(*float64)
259+
h.Assert(t, *flagMinOutput == 0.0 && *flagMaxOutput == 5.1, "Flag %s max should have been parsed from cmdline and min set to 0.0", flagArg)
260+
261+
// Min and Max are set to separate values
262+
cli = getTestCLI()
263+
cli.Float64MinMaxRangeFlags(flagName, nil, nil, "Test")
264+
os.Args = []string{"ec2-instance-selector", "--" + flagMinArg, "10.1", "--" + flagMaxArg, "500.1"}
265+
flags, err = cli.ParseFlags()
266+
h.Ok(t, err)
267+
flagMinOutput = flags[flagMinArg].(*float64)
268+
flagMaxOutput = flags[flagMaxArg].(*float64)
269+
h.Assert(t, *flagMinOutput == 10.1 && *flagMaxOutput == 500.1, "Flag %s min and max should have been parsed from cmdline", flagArg)
270+
}
271+
225272
func TestParseAndValidateFlags_ByteQuantityRange(t *testing.T) {
226273
flagName := "test-flag"
227274
flagMinArg := fmt.Sprintf("%s-%s", flagName, "min")

0 commit comments

Comments
 (0)