Skip to content

Commit 9f35f8e

Browse files
authored
Add estimates and pretty printing to exclusion status check (#1927)
* Add estimates and pretty printing to exclusion status check * Correct exclusion status logic for more than 1 SS per Pod
1 parent 61f8b88 commit 9f35f8e

File tree

2 files changed

+84
-13
lines changed

2 files changed

+84
-13
lines changed

kubectl-fdb/cmd/exclusion_status.go

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"fmt"
2626
"github.com/FoundationDB/fdb-kubernetes-operator/pkg/fdbstatus"
2727
"sort"
28+
"strconv"
2829
"time"
2930

3031
fdbv1beta2 "github.com/FoundationDB/fdb-kubernetes-operator/api/v1beta2"
@@ -126,15 +127,17 @@ kubectl fdb get exclusion-status c1 --interval=5m
126127

127128
type exclusionResult struct {
128129
id string
129-
storedBytes int
130130
estimate string
131+
storedBytes int
132+
timestamp time.Time
131133
}
132134

133135
func getExclusionStatus(cmd *cobra.Command, restConfig *rest.Config, kubeClient *kubernetes.Clientset, clientPod string, namespace string, ignoreFullyExcluded bool, interval time.Duration) error {
134136
timer := time.NewTicker(interval)
135-
previousRun := map[string]int{}
137+
previousRun := map[string]exclusionResult{}
136138

137139
for {
140+
// TODO: Keeping a stream open is probably more efficient.
138141
out, serr, err := executeCmd(restConfig, kubeClient, clientPod, namespace, "fdbcli --exec 'status json'")
139142
if err != nil {
140143
// If an error occurs retry
@@ -162,38 +165,48 @@ func getExclusionStatus(cmd *cobra.Command, restConfig *rest.Config, kubeClient
162165
}
163166

164167
var ongoingExclusions []exclusionResult
168+
timestamp := time.Now()
165169
for _, process := range status.Cluster.Processes {
166170
if !process.Excluded {
167171
continue
168172
}
169173

174+
// If more than one storage server per Pod is running we have to differentiate those processes. If the
175+
// process ID is not set, fall back to the instance ID.
176+
instance, ok := process.Locality[fdbv1beta2.FDBLocalityProcessIDKey]
177+
if !ok {
178+
instance = process.Locality[fdbv1beta2.FDBLocalityInstanceIDKey]
179+
}
180+
170181
if !ignoreFullyExcluded && len(process.Roles) == 0 {
171-
cmd.Println(process.Locality["instance_id"], "is fully excluded")
182+
cmd.Println(instance, "is fully excluded")
183+
continue
172184
}
173185

174-
instance := process.Locality["instance_id"]
175-
// TODO: Add estimate when an exclusion is done
176186
// TODO: Add progress bars
177187
for _, role := range process.Roles {
178188
roleClass := fdbv1beta2.ProcessClass(role.Role)
179189
if roleClass.IsStateful() {
180190
var estimate string
181191

182-
previousBytes, ok := previousRun[instance]
192+
previousResult, ok := previousRun[instance]
183193
if ok {
184-
// TODO calculate estimates for duration
185-
_ = previousBytes
194+
estimateDuration := time.Duration(role.StoredBytes/(previousResult.storedBytes-role.StoredBytes)) * timestamp.Sub(previousResult.timestamp)
195+
estimate = estimateDuration.String()
196+
} else {
186197
estimate = "N/A"
187198
}
188199

189-
// TODO: Check if StoredBytes is the correct value
190-
ongoingExclusions = append(ongoingExclusions, exclusionResult{
200+
result := exclusionResult{
191201
id: instance,
192202
storedBytes: role.StoredBytes,
193203
estimate: estimate,
194-
})
204+
timestamp: timestamp,
205+
}
206+
// TODO: Check if StoredBytes is the correct value
207+
ongoingExclusions = append(ongoingExclusions, result)
195208

196-
previousRun[instance] = role.StoredBytes
209+
previousRun[instance] = result
197210
}
198211
}
199212
}
@@ -208,7 +221,7 @@ func getExclusionStatus(cmd *cobra.Command, restConfig *rest.Config, kubeClient
208221
})
209222

210223
for _, exclusion := range ongoingExclusions {
211-
cmd.Printf("%s:\t %d bytes are left - estimate: %s\n", exclusion.id, exclusion.storedBytes, exclusion.estimate)
224+
cmd.Printf("%s:\t %s are left - estimate: %s\n", exclusion.id, prettyPrintStoredBytes(exclusion.storedBytes), exclusion.estimate)
212225
}
213226

214227
cmd.Println("There are", len(ongoingExclusions), "processes that are not fully excluded.")
@@ -218,3 +231,20 @@ func getExclusionStatus(cmd *cobra.Command, restConfig *rest.Config, kubeClient
218231

219232
return nil
220233
}
234+
235+
// prettyPrintStoredBytes will return a string that represents the storedBytes in a human-readable format.
236+
func prettyPrintStoredBytes(storedBytes int) string {
237+
units := []string{"", "Ki", "Mi", "Gi", "Ti", "Pi"}
238+
239+
currentBytes := float64(storedBytes)
240+
for _, unit := range units {
241+
if currentBytes < 1024 {
242+
return fmt.Sprintf("%3.2f%s", currentBytes, unit)
243+
}
244+
245+
currentBytes /= 1024
246+
}
247+
248+
// Fallback will be to printout the bytes.
249+
return strconv.Itoa(storedBytes)
250+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* exclusion_status_test.go
3+
*
4+
* This source file is part of the FoundationDB open source project
5+
*
6+
* Copyright 2021 Apple Inc. and the FoundationDB project authors
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
21+
package cmd
22+
23+
import (
24+
. "github.com/onsi/ginkgo/v2"
25+
. "github.com/onsi/gomega"
26+
)
27+
28+
var _ = Describe("[plugin] exclustion stats command", func() {
29+
DescribeTable("pretty printing the stored bytes", func(storedByes int, expected string) {
30+
Expect(prettyPrintStoredBytes(storedByes)).To(Equal(expected))
31+
},
32+
Entry("a few bytes", 1023, "1023.00"),
33+
Entry("two KiB", 2*1024, "2.00Ki"),
34+
Entry("two and a half KiB", 2*1024+512, "2.50Ki"),
35+
Entry("three MiB", 3*1024*1024, "3.00Mi"),
36+
Entry("four GiB", 4*1024*1024*1024, "4.00Gi"),
37+
Entry("five TiB", 5*1024*1024*1024*1024, "5.00Ti"),
38+
Entry("six Pib", 6*1024*1024*1024*1024*1024, "6.00Pi"),
39+
)
40+
41+
})

0 commit comments

Comments
 (0)