19320: Log instance price changes in crunch-run.txt.
authorTom Clegg <tom@curii.com>
Mon, 23 Jan 2023 15:31:41 +0000 (10:31 -0500)
committerTom Clegg <tom@curii.com>
Mon, 23 Jan 2023 15:31:41 +0000 (10:31 -0500)
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom@curii.com>

lib/cloud/price.go
lib/cloud/price_test.go [new file with mode: 0644]
lib/crunchrun/crunchrun.go
lib/crunchrun/crunchrun_test.go

index 234564b68431de40a77510a26017fd42f06a5c4e..59f5afc94b6174450edac5ffa00c920fff3d516d 100644 (file)
@@ -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 (file)
index 0000000..e2a4a7e
--- /dev/null
@@ -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]})
+}
index 1dd232d3ed2e7cecf92d5eb55164c1a3d2e76fc6..1f130a5b1003c441023953e862edbfac7ba650c0 100644 (file)
@@ -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 {
index 1e3b2eb5c10ad6ba3e1d38dc2a4cc457846c96e0..a5045863b0e1bc17cf5a372e290a9b8e2716b3df 100644 (file)
@@ -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 {