diff --git a/components/cluster/command/display.go b/components/cluster/command/display.go index d19b25be2c..298875d886 100644 --- a/components/cluster/command/display.go +++ b/components/cluster/command/display.go @@ -80,6 +80,7 @@ func newDisplayCmd() *cobra.Command { cmd.Flags().StringSliceVarP(&gOpt.Roles, "role", "R", nil, "Only display specified roles") cmd.Flags().StringSliceVarP(&gOpt.Nodes, "node", "N", nil, "Only display specified nodes") + cmd.Flags().BoolVar(&gOpt.ShowUptime, "uptime", false, "Display with uptime") cmd.Flags().BoolVar(&showDashboardOnly, "dashboard", false, "Only display TiDB Dashboard information") cmd.Flags().BoolVar(&showVersionOnly, "version", false, "Only display TiDB cluster version") diff --git a/components/dm/command/display.go b/components/dm/command/display.go index 785c843d53..670e6e4f43 100644 --- a/components/dm/command/display.go +++ b/components/dm/command/display.go @@ -54,6 +54,7 @@ func newDisplayCmd() *cobra.Command { cmd.Flags().StringSliceVarP(&gOpt.Roles, "role", "R", nil, "Only display specified roles") cmd.Flags().StringSliceVarP(&gOpt.Nodes, "node", "N", nil, "Only display specified nodes") cmd.Flags().BoolVar(&showVersionOnly, "version", false, "Only display DM cluster version") + cmd.Flags().BoolVar(&gOpt.ShowUptime, "uptime", false, "Display DM with uptime") return cmd } diff --git a/components/dm/spec/logic.go b/components/dm/spec/logic.go index 2168f33fdd..189154e830 100644 --- a/components/dm/spec/logic.go +++ b/components/dm/spec/logic.go @@ -15,9 +15,11 @@ package spec import ( "context" + "crypto/tls" "fmt" "path/filepath" "strings" + "time" "github.com/pingcap/tiup/pkg/logger/log" "github.com/pingcap/tiup/pkg/meta" @@ -88,6 +90,9 @@ func (c *DMMasterComponent) Instances() []Instance { s.DataDir, }, StatusFn: s.Status, + UptimeFn: func(tlsCfg *tls.Config) time.Duration { + return spec.UptimeByHost(s.Host, s.Port, tlsCfg) + }, }, topo: c.Topology, }) @@ -216,6 +221,9 @@ func (c *DMWorkerComponent) Instances() []Instance { s.DataDir, }, StatusFn: s.Status, + UptimeFn: func(tlsCfg *tls.Config) time.Duration { + return spec.UptimeByHost(s.Host, s.Port, tlsCfg) + }, }, topo: c.Topology, }) diff --git a/go.mod b/go.mod index 53e1e146a5..cf713efe07 100644 --- a/go.mod +++ b/go.mod @@ -46,6 +46,7 @@ require ( github.com/pingcap/log v0.0.0-20201112100606-8f1e84a3abc8 // indirect github.com/pingcap/tidb-insight v0.3.2 github.com/prometheus/client_model v0.2.0 + github.com/prometheus/common v0.15.0 github.com/prometheus/prom2json v1.3.0 github.com/r3labs/diff/v2 v2.12.0 github.com/relex/aini v1.2.1 diff --git a/pkg/cluster/manager/display.go b/pkg/cluster/manager/display.go index 656908f496..e3413f66e5 100644 --- a/pkg/cluster/manager/display.go +++ b/pkg/cluster/manager/display.go @@ -17,6 +17,7 @@ import ( "context" "errors" "fmt" + "math" "sort" "strings" "time" @@ -44,6 +45,7 @@ type InstInfo struct { Ports string `json:"ports"` OsArch string `json:"os_arch"` Status string `json:"status"` + Since string `json:"since"` DataDir string `json:"data_dir"` DeployDir string `json:"deploy_dir"` @@ -87,22 +89,28 @@ func (m *Manager) Display(name string, opt operator.Options) error { } // display topology - clusterTable := [][]string{ - // Header - {"ID", "Role", "Host", "Ports", "OS/Arch", "Status", "Data Dir", "Deploy Dir"}, + var clusterTable [][]string + if opt.ShowUptime { + clusterTable = append(clusterTable, []string{"ID", "Role", "Host", "Ports", "OS/Arch", "Status", "Since", "Data Dir", "Deploy Dir"}) + } else { + clusterTable = append(clusterTable, []string{"ID", "Role", "Host", "Ports", "OS/Arch", "Status", "Data Dir", "Deploy Dir"}) } + masterActive := make([]string, 0) for _, v := range clusterInstInfos { - clusterTable = append(clusterTable, []string{ + row := []string{ color.CyanString(v.ID), v.Role, v.Host, v.Ports, v.OsArch, formatInstanceStatus(v.Status), - v.DataDir, - v.DeployDir, - }) + } + if opt.ShowUptime { + row = append(row, v.Since) + } + row = append(row, v.DataDir, v.DeployDir) + clusterTable = append(clusterTable, row) if v.ComponentName != spec.ComponentPD && v.ComponentName != spec.ComponentDMMaster { continue @@ -237,18 +245,28 @@ func (m *Manager) GetClusterTopology(name string, opt operator.Options) ([]InstI status = ins.Status(tlsCfg, masterActive...) } - // Query the service status - if status == "-" { + since := "-" + if opt.ShowUptime { + since = formatInstanceSince(ins.Uptime(tlsCfg)) + } + + // Query the service status and uptime + if status == "-" || (opt.ShowUptime && since == "-") { e, found := ctxt.GetInner(ctx).GetExecutor(ins.GetHost()) if found { active, _ := operator.GetServiceStatus(ctx, e, ins.ServiceName()) - if parts := strings.Split(strings.TrimSpace(active), " "); len(parts) > 2 { - if parts[1] == "active" { - status = "Up" - } else { - status = parts[1] + if status == "-" { + if parts := strings.Split(strings.TrimSpace(active), " "); len(parts) > 2 { + if parts[1] == "active" { + status = "Up" + } else { + status = parts[1] + } } } + if opt.ShowUptime && since == "-" { + since = formatInstanceSince(parseSystemctlSince(active)) + } } } @@ -268,6 +286,7 @@ func (m *Manager) GetClusterTopology(name string, opt operator.Options) ([]InstI DeployDir: deployDir, ComponentName: ins.ComponentName(), Port: ins.GetPort(), + Since: since, }) }) @@ -303,7 +322,7 @@ func formatInstanceStatus(status string) string { return color.HiGreenString(status) case startsWith("up", "healthy", "free"): return color.GreenString(status) - case startsWith("down", "err"): // down, down|ui + case startsWith("down", "err", "inactive"): // down, down|ui return color.RedString(status) case startsWith("tombstone", "disconnected", "n/a"), strings.Contains(status, "offline"): return color.YellowString(status) @@ -312,6 +331,71 @@ func formatInstanceStatus(status string) string { } } +func formatInstanceSince(uptime time.Duration) string { + if uptime == 0 { + return "-" + } + + d := int64(uptime.Hours() / 24) + h := int64(math.Mod(uptime.Hours(), 24)) + m := int64(math.Mod(uptime.Minutes(), 60)) + s := int64(math.Mod(uptime.Seconds(), 60)) + + chunks := []struct { + unit string + value int64 + }{ + {"d", d}, + {"h", h}, + {"m", m}, + {"s", s}, + } + + parts := []string{} + + for _, chunk := range chunks { + switch chunk.value { + case 0: + continue + default: + parts = append(parts, fmt.Sprintf("%d%s", chunk.value, chunk.unit)) + } + } + + return strings.Join(parts, "") +} + +// `systemctl status xxx.service` returns as below +// Active: active (running) since Sat 2021-03-27 10:51:11 CST; 41min ago +func parseSystemctlSince(str string) (dur time.Duration) { + // if service is not found or other error, don't need to parse it + if str == "" { + return 0 + } + defer func() { + if dur == 0 { + log.Warnf("failed to parse systemctl since '%s'", str) + } + }() + parts := strings.Split(str, ";") + if len(parts) != 2 { + return + } + parts = strings.Split(parts[0], " ") + if len(parts) < 3 { + return + } + + dateStr := strings.Join(parts[len(parts)-3:], " ") + + tm, err := time.Parse("2006-01-02 15:04:05 MST", dateStr) + if err != nil { + return + } + + return time.Since(tm) +} + // SetSSHKeySet set ssh key set. func SetSSHKeySet(ctx context.Context, privateKeyPath string, publicKeyPath string) error { ctxt.GetInner(ctx).PrivateKeyPath = privateKeyPath diff --git a/pkg/cluster/operation/operation.go b/pkg/cluster/operation/operation.go index 01c96b0239..7a79130cc8 100644 --- a/pkg/cluster/operation/operation.go +++ b/pkg/cluster/operation/operation.go @@ -40,6 +40,9 @@ type Options struct { // Some data will be retained when destroying instances RetainDataRoles []string RetainDataNodes []string + + // Show uptime or not + ShowUptime bool } // Operation represents the type of cluster operation diff --git a/pkg/cluster/spec/alertmanager.go b/pkg/cluster/spec/alertmanager.go index aee6a2e96a..2ea74c6ac3 100644 --- a/pkg/cluster/spec/alertmanager.go +++ b/pkg/cluster/spec/alertmanager.go @@ -18,6 +18,7 @@ import ( "crypto/tls" "fmt" "path/filepath" + "time" "github.com/pingcap/tiup/pkg/cluster/ctxt" "github.com/pingcap/tiup/pkg/cluster/template/config" @@ -100,7 +101,10 @@ func (c *AlertManagerComponent) Instances() []Instance { s.DataDir, }, StatusFn: func(_ *tls.Config, _ ...string) string { - return "-" + return statusByHost(s.Host, s.WebPort, "/-/ready", nil) + }, + UptimeFn: func(tlsCfg *tls.Config) time.Duration { + return UptimeByHost(s.Host, s.WebPort, tlsCfg) }, }, topo: c.Topology, diff --git a/pkg/cluster/spec/cdc.go b/pkg/cluster/spec/cdc.go index 2949f17f30..a37c74f100 100644 --- a/pkg/cluster/spec/cdc.go +++ b/pkg/cluster/spec/cdc.go @@ -18,6 +18,7 @@ import ( "crypto/tls" "fmt" "path/filepath" + "time" "github.com/pingcap/tiup/pkg/cluster/ctxt" "github.com/pingcap/tiup/pkg/cluster/template/scripts" @@ -95,12 +96,10 @@ func (c *CDCComponent) Instances() []Instance { s.DeployDir, }, StatusFn: func(tlsCfg *tls.Config, _ ...string) string { - scheme := "http" - if tlsCfg != nil { - scheme = "https" - } - url := fmt.Sprintf("%s://%s:%d/status", scheme, s.Host, s.Port) - return statusByURL(url, tlsCfg) + return statusByHost(s.Host, s.Port, "/status", tlsCfg) + }, + UptimeFn: func(tlsCfg *tls.Config) time.Duration { + return UptimeByHost(s.Host, s.Port, tlsCfg) }, }, c.Topology}) } diff --git a/pkg/cluster/spec/drainer.go b/pkg/cluster/spec/drainer.go index 9b3370770c..858b9a1e85 100644 --- a/pkg/cluster/spec/drainer.go +++ b/pkg/cluster/spec/drainer.go @@ -20,6 +20,7 @@ import ( "os" "path/filepath" "strconv" + "time" "github.com/pingcap/tiup/pkg/cluster/ctxt" "github.com/pingcap/tiup/pkg/cluster/template/scripts" @@ -98,12 +99,10 @@ func (c *DrainerComponent) Instances() []Instance { s.DataDir, }, StatusFn: func(tlsCfg *tls.Config, _ ...string) string { - scheme := "http" - if tlsCfg != nil { - scheme = "https" - } - url := fmt.Sprintf("%s://%s:%d/status", scheme, s.Host, s.Port) - return statusByURL(url, tlsCfg) + return statusByHost(s.Host, s.Port, "/status", tlsCfg) + }, + UptimeFn: func(tlsCfg *tls.Config) time.Duration { + return UptimeByHost(s.Host, s.Port, tlsCfg) }, }, c.Topology}) } diff --git a/pkg/cluster/spec/grafana.go b/pkg/cluster/spec/grafana.go index ce0e86815a..836cf8d42a 100644 --- a/pkg/cluster/spec/grafana.go +++ b/pkg/cluster/spec/grafana.go @@ -20,6 +20,7 @@ import ( "path/filepath" "reflect" "strings" + "time" "github.com/pingcap/errors" "github.com/pingcap/tiup/pkg/cluster/ctxt" @@ -101,7 +102,10 @@ func (c *GrafanaComponent) Instances() []Instance { s.DeployDir, }, StatusFn: func(_ *tls.Config, _ ...string) string { - return "-" + return statusByHost(s.Host, s.Port, "", nil) + }, + UptimeFn: func(tlsCfg *tls.Config) time.Duration { + return UptimeByHost(s.Host, s.Port, tlsCfg) }, }, topo: c.Topology, diff --git a/pkg/cluster/spec/instance.go b/pkg/cluster/spec/instance.go index 0738b91da5..2866d0f299 100644 --- a/pkg/cluster/spec/instance.go +++ b/pkg/cluster/spec/instance.go @@ -95,6 +95,7 @@ type Instance interface { UsedPorts() []int UsedDirs() []string Status(tlsCfg *tls.Config, pdList ...string) string + Uptime(tlsCfg *tls.Config) time.Duration DataDir() string LogDir() string OS() string // only linux supported now @@ -138,6 +139,7 @@ type BaseInstance struct { Ports []int Dirs []string StatusFn func(tlsCfg *tls.Config, pdHosts ...string) string + UptimeFn func(tlsCfg *tls.Config) time.Duration } // Ready implements Instance interface @@ -448,3 +450,8 @@ func (i *BaseInstance) UsedDirs() []string { func (i *BaseInstance) Status(tlsCfg *tls.Config, pdList ...string) string { return i.StatusFn(tlsCfg, pdList...) } + +// Uptime implements Instance interface +func (i *BaseInstance) Uptime(tlsCfg *tls.Config) time.Duration { + return i.UptimeFn(tlsCfg) +} diff --git a/pkg/cluster/spec/pd.go b/pkg/cluster/spec/pd.go index 83d611f0b9..f006e9f71d 100644 --- a/pkg/cluster/spec/pd.go +++ b/pkg/cluster/spec/pd.go @@ -131,6 +131,9 @@ func (c *PDComponent) Instances() []Instance { s.DataDir, }, StatusFn: s.Status, + UptimeFn: func(tlsCfg *tls.Config) time.Duration { + return UptimeByHost(s.Host, s.ClientPort, tlsCfg) + }, }, topo: c.Topology, }) diff --git a/pkg/cluster/spec/prometheus.go b/pkg/cluster/spec/prometheus.go index c369ff59a9..fad408e7de 100644 --- a/pkg/cluster/spec/prometheus.go +++ b/pkg/cluster/spec/prometheus.go @@ -21,6 +21,7 @@ import ( "path/filepath" "reflect" "strings" + "time" "github.com/pingcap/errors" "github.com/pingcap/tiup/pkg/cluster/ctxt" @@ -116,7 +117,10 @@ func (c *MonitorComponent) Instances() []Instance { s.DataDir, }, StatusFn: func(_ *tls.Config, _ ...string) string { - return "-" + return statusByHost(s.Host, s.Port, "/-/ready", nil) + }, + UptimeFn: func(tlsCfg *tls.Config) time.Duration { + return UptimeByHost(s.Host, s.Port, tlsCfg) }, }, c.Topology}) } diff --git a/pkg/cluster/spec/pump.go b/pkg/cluster/spec/pump.go index 5049d976f6..ccd0120070 100644 --- a/pkg/cluster/spec/pump.go +++ b/pkg/cluster/spec/pump.go @@ -20,6 +20,7 @@ import ( "os" "path/filepath" "strconv" + "time" "github.com/pingcap/tiup/pkg/cluster/ctxt" "github.com/pingcap/tiup/pkg/cluster/template/scripts" @@ -97,12 +98,10 @@ func (c *PumpComponent) Instances() []Instance { s.DataDir, }, StatusFn: func(tlsCfg *tls.Config, _ ...string) string { - scheme := "http" - if tlsCfg != nil { - scheme = "https" - } - url := fmt.Sprintf("%s://%s:%d/status", scheme, s.Host, s.Port) - return statusByURL(url, tlsCfg) + return statusByHost(s.Host, s.Port, "/status", tlsCfg) + }, + UptimeFn: func(tlsCfg *tls.Config) time.Duration { + return UptimeByHost(s.Host, s.Port, tlsCfg) }, }, c.Topology}) } diff --git a/pkg/cluster/spec/spec.go b/pkg/cluster/spec/spec.go index 7beb8eea9b..6ab09de69c 100644 --- a/pkg/cluster/spec/spec.go +++ b/pkg/cluster/spec/spec.go @@ -34,6 +34,9 @@ import ( const ( // Timeout in second when quering node status statusQueryTimeout = 10 * time.Second + + // the prometheus metric name of start time of the process since unix epoch in seconds. + promMetricStartTimeSeconds = "process_start_time_seconds" ) // general role names diff --git a/pkg/cluster/spec/tidb.go b/pkg/cluster/spec/tidb.go index db5f45158a..333b109429 100644 --- a/pkg/cluster/spec/tidb.go +++ b/pkg/cluster/spec/tidb.go @@ -19,6 +19,7 @@ import ( "fmt" "os" "path/filepath" + "time" "github.com/pingcap/tiup/pkg/cluster/ctxt" "github.com/pingcap/tiup/pkg/cluster/template/scripts" @@ -97,12 +98,10 @@ func (c *TiDBComponent) Instances() []Instance { s.DeployDir, }, StatusFn: func(tlsCfg *tls.Config, _ ...string) string { - scheme := "http" - if tlsCfg != nil { - scheme = "https" - } - url := fmt.Sprintf("%s://%s:%d/status", scheme, s.Host, s.StatusPort) - return statusByURL(url, tlsCfg) + return statusByHost(s.Host, s.StatusPort, "/status", tlsCfg) + }, + UptimeFn: func(tlsCfg *tls.Config) time.Duration { + return UptimeByHost(s.Host, s.StatusPort, tlsCfg) }, }, c.Topology}) } diff --git a/pkg/cluster/spec/tiflash.go b/pkg/cluster/spec/tiflash.go index 7359afbddb..a8e1abef2d 100644 --- a/pkg/cluster/spec/tiflash.go +++ b/pkg/cluster/spec/tiflash.go @@ -225,6 +225,9 @@ func (c *TiFlashComponent) Instances() []Instance { s.DataDir, }, StatusFn: s.Status, + UptimeFn: func(tlsCfg *tls.Config) time.Duration { + return 0 + }, }, c.Topology}) } return ins diff --git a/pkg/cluster/spec/tikv.go b/pkg/cluster/spec/tikv.go index eb180bdd72..4a40ef6213 100644 --- a/pkg/cluster/spec/tikv.go +++ b/pkg/cluster/spec/tikv.go @@ -173,6 +173,9 @@ func (c *TiKVComponent) Instances() []Instance { s.DataDir, }, StatusFn: s.Status, + UptimeFn: func(tlsCfg *tls.Config) time.Duration { + return UptimeByHost(s.Host, s.StatusPort, tlsCfg) + }, }, c.Topology}) } return ins diff --git a/pkg/cluster/spec/tispark.go b/pkg/cluster/spec/tispark.go index 0f90371769..4ebcfac882 100644 --- a/pkg/cluster/spec/tispark.go +++ b/pkg/cluster/spec/tispark.go @@ -21,6 +21,7 @@ import ( "path/filepath" "reflect" "strings" + "time" "github.com/google/uuid" "github.com/pingcap/errors" @@ -70,16 +71,6 @@ func (s *TiSparkMasterSpec) IsImported() bool { return s.Imported } -// Status queries current status of the instance -func (s *TiSparkMasterSpec) Status(tlsCfg *tls.Config, pdList ...string) string { - scheme := "http" - if tlsCfg != nil { - scheme = "https" - } - url := fmt.Sprintf("%s://%s:%d/", scheme, s.Host, s.WebPort) - return statusByURL(url, tlsCfg) -} - // TiSparkWorkerSpec is the topology specification for TiSpark slave nodes type TiSparkWorkerSpec struct { Host string `yaml:"host"` @@ -115,16 +106,6 @@ func (s *TiSparkWorkerSpec) IsImported() bool { return s.Imported } -// Status queries current status of the instance -func (s *TiSparkWorkerSpec) Status(tlsCfg *tls.Config, pdList ...string) string { - scheme := "http" - if tlsCfg != nil { - scheme = "https" - } - url := fmt.Sprintf("%s://%s:%d/", scheme, s.Host, s.WebPort) - return statusByURL(url, tlsCfg) -} - // TiSparkMasterComponent represents TiSpark master component. type TiSparkMasterComponent struct{ Topology *Specification } @@ -157,7 +138,12 @@ func (c *TiSparkMasterComponent) Instances() []Instance { Dirs: []string{ s.DeployDir, }, - StatusFn: s.Status, + StatusFn: func(tlsCfg *tls.Config, _ ...string) string { + return statusByHost(s.Host, s.WebPort, "", tlsCfg) + }, + UptimeFn: func(tlsCfg *tls.Config) time.Duration { + return 0 + }, }, topo: c.Topology, }) @@ -333,7 +319,12 @@ func (c *TiSparkWorkerComponent) Instances() []Instance { Dirs: []string{ s.DeployDir, }, - StatusFn: s.Status, + StatusFn: func(tlsCfg *tls.Config, _ ...string) string { + return statusByHost(s.Host, s.WebPort, "", tlsCfg) + }, + UptimeFn: func(tlsCfg *tls.Config) time.Duration { + return 0 + }, }, topo: c.Topology, }) diff --git a/pkg/cluster/spec/util.go b/pkg/cluster/spec/util.go index 8f31a5f833..c2632cc450 100644 --- a/pkg/cluster/spec/util.go +++ b/pkg/cluster/spec/util.go @@ -14,14 +14,17 @@ package spec import ( + "bytes" "crypto/tls" "fmt" "path/filepath" "reflect" "strings" + "time" "github.com/pingcap/tiup/pkg/utils" "github.com/pingcap/tiup/pkg/version" + "github.com/prometheus/common/expfmt" "go.etcd.io/etcd/pkg/transport" ) @@ -119,10 +122,19 @@ func LoadClientCert(dir string) (*tls.Config, error) { }.ClientConfig() } -// statusByURL queries current status of the instance by http status api. -func statusByURL(url string, tlsCfg *tls.Config) string { +// statusByHost queries current status of the instance by http status api. +func statusByHost(host string, port int, path string, tlsCfg *tls.Config) string { client := utils.NewHTTPClient(statusQueryTimeout, tlsCfg) + scheme := "http" + if tlsCfg != nil { + scheme = "https" + } + if path == "" { + path = "/" + } + url := fmt.Sprintf("%s://%s:%d%s", scheme, host, port, path) + // body doesn't have any status section needed body, err := client.Get(url) if err != nil || body == nil { @@ -131,6 +143,43 @@ func statusByURL(url string, tlsCfg *tls.Config) string { return "Up" } +// UptimeByHost queries current uptime of the instance by http Prometheus metric api. +func UptimeByHost(host string, port int, tlsCfg *tls.Config) time.Duration { + scheme := "http" + if tlsCfg != nil { + scheme = "https" + } + url := fmt.Sprintf("%s://%s:%d/metrics", scheme, host, port) + + client := utils.NewHTTPClient(statusQueryTimeout, tlsCfg) + + body, err := client.Get(url) + if err != nil || body == nil { + return 0 + } + + var parser expfmt.TextParser + reader := bytes.NewReader(body) + mf, err := parser.TextToMetricFamilies(reader) + if err != nil { + return 0 + } + + now := time.Now() + for k, v := range mf { + if k == promMetricStartTimeSeconds { + ms := v.GetMetric() + if len(ms) >= 1 { + startTime := ms[0].Gauge.GetValue() + return now.Sub(time.Unix(int64(startTime), 0)) + } + return 0 + } + } + + return 0 +} + // Abs returns the absolute path func Abs(user, path string) string { // trim whitespaces before joining diff --git a/tests/tiup-cluster/script/cmd_subtest.sh b/tests/tiup-cluster/script/cmd_subtest.sh index 9c9202b419..3e54c34d98 100755 --- a/tests/tiup-cluster/script/cmd_subtest.sh +++ b/tests/tiup-cluster/script/cmd_subtest.sh @@ -109,6 +109,11 @@ function cmd_subtest() { echo "$display_result" | grep "Cluster version" echo "$display_result" | grep "Dashboard URL" echo "$display_result" | grep "Total nodes" + echo "$display_result" | grep -v "Since" + + # display with --uptime should show process uptime + display_result=`tiup-cluster $client display $name --uptime` + echo "$display_result" | grep "Since" # Test rename tiup-cluster $client rename $name "tmp-cluster-name" diff --git a/tests/tiup-dm/test_cmd.sh b/tests/tiup-dm/test_cmd.sh index 1719c9ceee..3e861e6e42 100755 --- a/tests/tiup-dm/test_cmd.sh +++ b/tests/tiup-dm/test_cmd.sh @@ -62,6 +62,7 @@ tiup-dm --yes stop $name tiup-dm --yes restart $name tiup-dm display $name +tiup-dm display $name --uptime total_sub_one=12