From c138f58b21edb574b101588f6fc61dce8a98ed3e Mon Sep 17 00:00:00 2001 From: Tom Clegg Date: Mon, 23 Jan 2023 10:31:41 -0500 Subject: [PATCH] 19320: Log instance price changes in crunch-run.txt. Arvados-DCO-1.1-Signed-off-by: Tom Clegg --- lib/cloud/price.go | 4 ++-- lib/cloud/price_test.go | 32 ++++++++++++++++++++++++++++++++ lib/crunchrun/crunchrun.go | 10 ++++++++++ lib/crunchrun/crunchrun_test.go | 11 ++++++++++- 4 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 lib/cloud/price_test.go diff --git a/lib/cloud/price.go b/lib/cloud/price.go index 234564b684..59f5afc94b 100644 --- a/lib/cloud/price.go +++ b/lib/cloud/price.go @@ -10,9 +10,9 @@ import ( // NormalizePriceHistory de-duplicates and sorts instance prices, most // recent first. -// -// The provided slice is modified in place. func NormalizePriceHistory(prices []InstancePrice) []InstancePrice { + // copy provided slice instead of modifying it in place + prices = append([]InstancePrice(nil), prices...) // sort by timestamp, newest first sort.Slice(prices, func(i, j int) bool { return prices[i].StartTime.After(prices[j].StartTime) diff --git a/lib/cloud/price_test.go b/lib/cloud/price_test.go new file mode 100644 index 0000000000..e2a4a7e13c --- /dev/null +++ b/lib/cloud/price_test.go @@ -0,0 +1,32 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +package cloud + +import ( + "testing" + "time" + + . "gopkg.in/check.v1" +) + +func Test(t *testing.T) { TestingT(t) } + +type cloudSuite struct{} + +var _ = Suite(&cloudSuite{}) + +func (s *cloudSuite) TestNormalizePriceHistory(c *C) { + t0, err := time.Parse(time.RFC3339, "2023-01-01T01:00:00Z") + c.Assert(err, IsNil) + h := []InstancePrice{ + {t0.Add(1 * time.Minute), 1.0}, + {t0.Add(4 * time.Minute), 1.2}, // drop: unchanged price + {t0.Add(5 * time.Minute), 1.1}, + {t0.Add(3 * time.Minute), 1.2}, + {t0.Add(5 * time.Minute), 1.1}, // drop: duplicate + {t0.Add(2 * time.Minute), 1.0}, // drop: out of order, unchanged price + } + c.Check(NormalizePriceHistory(h), DeepEquals, []InstancePrice{h[2], h[3], h[0]}) +} diff --git a/lib/crunchrun/crunchrun.go b/lib/crunchrun/crunchrun.go index 1dd232d3ed..1f130a5b10 100644 --- a/lib/crunchrun/crunchrun.go +++ b/lib/crunchrun/crunchrun.go @@ -2251,7 +2251,17 @@ func (cr *ContainerRunner) loadPrices() { } cr.pricesLock.Lock() defer cr.pricesLock.Unlock() + var lastKnown time.Time + if len(cr.prices) > 0 { + lastKnown = cr.prices[0].StartTime + } cr.prices = cloud.NormalizePriceHistory(append(prices, cr.prices...)) + for i := len(cr.prices) - 1; i >= 0; i-- { + price := cr.prices[i] + if price.StartTime.After(lastKnown) { + cr.CrunchLog.Printf("Instance price changed to %#.3g at %s", price.Price, price.StartTime.UTC()) + } + } } func (cr *ContainerRunner) calculateCost(now time.Time) float64 { diff --git a/lib/crunchrun/crunchrun_test.go b/lib/crunchrun/crunchrun_test.go index 1e3b2eb5c1..a5045863b0 100644 --- a/lib/crunchrun/crunchrun_test.go +++ b/lib/crunchrun/crunchrun_test.go @@ -12,6 +12,7 @@ import ( "fmt" "io" "io/ioutil" + "log" "os" "os/exec" "regexp" @@ -2076,7 +2077,10 @@ func (s *TestSuite) TestCalculateCost(c *C) { defer func(s string) { lockdir = s }(lockdir) lockdir = c.MkDir() now := time.Now() - cr := ContainerRunner{costStartTime: now.Add(-time.Hour)} + cr := s.runner + cr.costStartTime = now.Add(-time.Hour) + var logbuf bytes.Buffer + cr.CrunchLog.Immediate = log.New(&logbuf, "", 0) // if there's no InstanceType env var, cost is calculated as 0 os.Unsetenv("InstanceType") @@ -2106,6 +2110,7 @@ func (s *TestSuite) TestCalculateCost(c *C) { // next update (via --list + SIGUSR2) tells us the spot price // increased to $3/h 15 minutes ago j, err = json.Marshal([]cloud.InstancePrice{ + {StartTime: now.Add(-time.Hour / 3), Price: 2.0}, // dup of -time.Hour/2 price {StartTime: now.Add(-time.Hour / 4), Price: 3.0}, }) c.Assert(err, IsNil) @@ -2113,6 +2118,10 @@ func (s *TestSuite) TestCalculateCost(c *C) { cr.loadPrices() cost = cr.calculateCost(now) c.Check(cost, Equals, 1.0/2+2.0/4+3.0/4) + + c.Logf("%s", logbuf.String()) + c.Check(logbuf.String(), Matches, `(?ms).*Instance price changed to 1\.00 at 20.* changed to 2\.00 .* changed to 3\.00 .*`) + c.Check(logbuf.String(), Not(Matches), `(?ms).*changed to 2\.00 .* changed to 2\.00 .*`) } type FakeProcess struct { -- 2.30.2