Prechádzať zdrojové kódy

Merge pull request #905 from kubecost/mmd/assetsetrange-start-end-minutes

Add Start, End, and Minutes to AssetSetRange
Michael Dresser 4 rokov pred
rodič
commit
a994079ca4
2 zmenil súbory, kde vykonal 327 pridanie a 0 odobranie
  1. 65 0
      pkg/kubecost/asset.go
  2. 262 0
      pkg/kubecost/asset_test.go

+ 65 - 0
pkg/kubecost/asset.go

@@ -3066,6 +3066,71 @@ func (asr *AssetSetRange) Window() Window {
 	return NewWindow(&start, &end)
 	return NewWindow(&start, &end)
 }
 }
 
 
+// Start returns the earliest start of all Assets in the AssetSetRange.
+// It returns an error if there are no assets
+func (asr *AssetSetRange) Start() (time.Time, error) {
+	start := time.Time{}
+	firstStartNotSet := true
+	asr.Each(func(i int, as *AssetSet) {
+		as.Each(func(s string, a Asset) {
+			if firstStartNotSet {
+				start = a.Start()
+				firstStartNotSet = false
+			}
+			if a.Start().Before(start) {
+				start = a.Start()
+			}
+		})
+	})
+
+	if firstStartNotSet {
+		return start, fmt.Errorf("had no data to compute a start from")
+	}
+
+	return start, nil
+}
+
+// End returns the latest end of all Assets in the AssetSetRange.
+// It returns an error if there are no assets.
+func (asr *AssetSetRange) End() (time.Time, error) {
+	end := time.Time{}
+	firstEndNotSet := true
+	asr.Each(func(i int, as *AssetSet) {
+		as.Each(func(s string, a Asset) {
+			if firstEndNotSet {
+				end = a.End()
+				firstEndNotSet = false
+			}
+			if a.End().After(end) {
+				end = a.End()
+			}
+		})
+	})
+
+	if firstEndNotSet {
+		return end, fmt.Errorf("had no data to compute an end from")
+	}
+
+	return end, nil
+}
+
+// Minutes returns the duration, in minutes, between the earliest start
+// and the latest end of all assets in the AssetSetRange.
+func (asr *AssetSetRange) Minutes() float64 {
+	start, err := asr.Start()
+	if err != nil {
+		return 0
+	}
+	end, err := asr.End()
+	if err != nil {
+		return 0
+	}
+
+	duration := end.Sub(start)
+
+	return duration.Minutes()
+}
+
 // Returns true if string slices a and b contain all of the same strings, in any order.
 // Returns true if string slices a and b contain all of the same strings, in any order.
 func sameContents(a, b []string) bool {
 func sameContents(a, b []string) bool {
 	if len(a) != len(b) {
 	if len(a) != len(b) {

+ 262 - 0
pkg/kubecost/asset_test.go

@@ -1094,3 +1094,265 @@ func TestAssetToExternalAllocation(t *testing.T) {
 		t.Fatalf("expected external allocation with TotalCost %f; got %f", 10.00, alloc.TotalCost())
 		t.Fatalf("expected external allocation with TotalCost %f; got %f", 10.00, alloc.TotalCost())
 	}
 	}
 }
 }
+
+func TestAssetSetRange_Start(t *testing.T) {
+	tests := []struct {
+		name string
+		arg  *AssetSetRange
+
+		expectError bool
+		expected    time.Time
+	}{
+		{
+			name: "Empty ASR",
+			arg:  nil,
+
+			expectError: true,
+		},
+		{
+			name: "Single asset",
+			arg: &AssetSetRange{
+				assets: []*AssetSet{
+					&AssetSet{
+						assets: map[string]Asset{
+							"a": &Node{
+								start: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
+							},
+						},
+					},
+				},
+			},
+
+			expected: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
+		},
+		{
+			name: "Two assets",
+			arg: &AssetSetRange{
+				assets: []*AssetSet{
+					&AssetSet{
+						assets: map[string]Asset{
+							"a": &Node{
+								start: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
+							},
+							"b": &Node{
+								start: time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
+							},
+						},
+					},
+				},
+			},
+
+			expected: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
+		},
+		{
+			name: "Two AssetSets",
+			arg: &AssetSetRange{
+				assets: []*AssetSet{
+					&AssetSet{
+						assets: map[string]Asset{
+							"a": &Node{
+								start: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
+							},
+						},
+					},
+					&AssetSet{
+						assets: map[string]Asset{
+							"b": &Node{
+								start: time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
+							},
+						},
+					},
+				},
+			},
+
+			expected: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
+		},
+	}
+
+	for _, test := range tests {
+		result, err := test.arg.Start()
+		if test.expectError && err != nil {
+			continue
+		}
+
+		if test.expectError && err == nil {
+			t.Errorf("%s: expected error and got none", test.name)
+		} else if result != test.expected {
+			t.Errorf("%s: expected %s but got %s", test.name, test.expected, result)
+		}
+	}
+}
+
+func TestAssetSetRange_End(t *testing.T) {
+	tests := []struct {
+		name string
+		arg  *AssetSetRange
+
+		expectError bool
+		expected    time.Time
+	}{
+		{
+			name: "Empty ASR",
+			arg:  nil,
+
+			expectError: true,
+		},
+		{
+			name: "Single asset",
+			arg: &AssetSetRange{
+				assets: []*AssetSet{
+					&AssetSet{
+						assets: map[string]Asset{
+							"a": &Node{
+								end: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
+							},
+						},
+					},
+				},
+			},
+
+			expected: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
+		},
+		{
+			name: "Two assets",
+			arg: &AssetSetRange{
+				assets: []*AssetSet{
+					&AssetSet{
+						assets: map[string]Asset{
+							"a": &Node{
+								end: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
+							},
+							"b": &Node{
+								end: time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
+							},
+						},
+					},
+				},
+			},
+
+			expected: time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
+		},
+		{
+			name: "Two AssetSets",
+			arg: &AssetSetRange{
+				assets: []*AssetSet{
+					&AssetSet{
+						assets: map[string]Asset{
+							"a": &Node{
+								end: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
+							},
+						},
+					},
+					&AssetSet{
+						assets: map[string]Asset{
+							"b": &Node{
+								end: time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
+							},
+						},
+					},
+				},
+			},
+
+			expected: time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
+		},
+	}
+
+	for _, test := range tests {
+		result, err := test.arg.End()
+		if test.expectError && err != nil {
+			continue
+		}
+
+		if test.expectError && err == nil {
+			t.Errorf("%s: expected error and got none", test.name)
+		} else if result != test.expected {
+			t.Errorf("%s: expected %s but got %s", test.name, test.expected, result)
+		}
+	}
+}
+
+func TestAssetSetRange_Minutes(t *testing.T) {
+	tests := []struct {
+		name string
+		arg  *AssetSetRange
+
+		expected float64
+	}{
+		{
+			name: "Empty ASR",
+			arg:  nil,
+
+			expected: 0,
+		},
+		{
+			name: "Single asset",
+			arg: &AssetSetRange{
+				assets: []*AssetSet{
+					&AssetSet{
+						assets: map[string]Asset{
+							"a": &Node{
+								start: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
+								end:   time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
+							},
+						},
+					},
+				},
+			},
+
+			expected: 24 * 60,
+		},
+		{
+			name: "Two assets",
+			arg: &AssetSetRange{
+				assets: []*AssetSet{
+					&AssetSet{
+						assets: map[string]Asset{
+							"a": &Node{
+								start: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
+								end:   time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
+							},
+							"b": &Node{
+								start: time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
+								end:   time.Date(1970, 1, 3, 0, 0, 0, 0, time.UTC),
+							},
+						},
+					},
+				},
+			},
+
+			expected: 2 * 24 * 60,
+		},
+		{
+			name: "Two AssetSets",
+			arg: &AssetSetRange{
+				assets: []*AssetSet{
+					&AssetSet{
+						assets: map[string]Asset{
+							"a": &Node{
+								start: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
+								end:   time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
+							},
+						},
+					},
+					&AssetSet{
+						assets: map[string]Asset{
+							"b": &Node{
+								start: time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC),
+								end:   time.Date(1970, 1, 3, 0, 0, 0, 0, time.UTC),
+							},
+						},
+					},
+				},
+			},
+
+			expected: 2 * 24 * 60,
+		},
+	}
+
+	for _, test := range tests {
+		result := test.arg.Minutes()
+		if result != test.expected {
+			t.Errorf("%s: expected %f but got %f", test.name, test.expected, result)
+		}
+	}
+}