Skip to content

Commit

Permalink
status: make the output of the command as a table
Browse files Browse the repository at this point in the history
Made the output of the status command as a table.
Have done it using tabwriter from the standard library.
Since tabwriter does not support ANSI escape codes, I need to remove
extra spaces from the table header to use colored text in the
second column of the table.

Closes tarantool#197
  • Loading branch information
askalt authored and LeonidVas committed May 16, 2023
1 parent d00a4d0 commit e5b41ee
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 41 deletions.
2 changes: 1 addition & 1 deletion cli/cmd/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ func internalDaemonStatusModule(cmdCtx *cmdcontext.CmdCtx, args []string) error
}

daemonCtx := daemon.NewDaemonCtx(opts)
log.Info(process_utils.ProcessStatus(daemonCtx.PIDFile).Text)
log.Info(process_utils.ProcessStatus(daemonCtx.PIDFile).String())

return nil
}
11 changes: 3 additions & 8 deletions cli/cmd/status.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package cmd

import (
"github.com/apex/log"
"github.com/spf13/cobra"
"github.com/tarantool/tt/cli/cmdcontext"
"github.com/tarantool/tt/cli/modules"
"github.com/tarantool/tt/cli/running"
"github.com/tarantool/tt/cli/status"
)

// NewStatusCmd creates status command.
Expand Down Expand Up @@ -35,11 +35,6 @@ func internalStatusModule(cmdCtx *cmdcontext.CmdCtx, args []string) error {
return err
}

for _, run := range runningCtx.Instances {
fullInstanceName := running.GetAppInstanceName(run)
procStatus := running.Status(&run)
log.Infof("%s: %s", procStatus.ColorSprint(fullInstanceName), procStatus.Text)
}

return nil
err := status.Status(runningCtx)
return err
}
33 changes: 22 additions & 11 deletions cli/process_utils/process_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ const defaultDirPerms = 0770
type ProcessState struct {
Code int
ColorSprint func(a ...interface{}) string
Text string
Status string
PID int
}

const (
Expand All @@ -34,17 +35,27 @@ const (

var (
ProcStateRunning = ProcessState{
ProcessRunningCode,
color.New(color.FgGreen).SprintFunc(),
"RUNNING. PID: %v."}
ProcStateStopped = ProcessState{ProcessStoppedCode,
color.New(color.FgYellow).SprintFunc(),
"NOT RUNNING"}
ProcStateDead = ProcessState{ProcessDeadCode,
color.New(color.FgRed).SprintFunc(),
"ERROR. The process is dead"}
Code: ProcessRunningCode,
ColorSprint: color.New(color.FgGreen).SprintFunc(),
Status: "RUNNING"}
ProcStateStopped = ProcessState{
Code: ProcessStoppedCode,
ColorSprint: color.New(color.FgYellow).SprintFunc(),
Status: "NOT RUNNING"}
ProcStateDead = ProcessState{
Code: ProcessDeadCode,
ColorSprint: color.New(color.FgRed).SprintFunc(),
Status: "ERROR. The process is dead"}
)

// String makes a string from ProcessState.
func (procState ProcessState) String() string {
if procState.Code == ProcessRunningCode {
return fmt.Sprintf("%s. PID: %d.", procState.Status, procState.PID)
}
return procState.Status
}

// GetPIDFromFile returns PID from the PIDFile.
func GetPIDFromFile(pidFileName string) (int, error) {
if _, err := os.Stat(pidFileName); err != nil {
Expand Down Expand Up @@ -167,7 +178,7 @@ func ProcessStatus(pidFile string) ProcessState {
}

procState := ProcStateRunning
procState.Text = fmt.Sprintf(procState.Text, pid)
procState.PID = pid
return procState
}

Expand Down
4 changes: 2 additions & 2 deletions cli/running/running.go
Original file line number Diff line number Diff line change
Expand Up @@ -516,12 +516,12 @@ func Status(run *InstanceCtx) process_utils.ProcessState {
func Logrotate(run *InstanceCtx) (string, error) {
pid, err := process_utils.GetPIDFromFile(run.PIDFile)
if err != nil {
return "", fmt.Errorf(instStateStopped.Text)
return "", fmt.Errorf(instStateStopped.String())
}

alive, err := process_utils.IsProcessAlive(pid)
if !alive {
return "", fmt.Errorf(instStateDead.Text)
return "", fmt.Errorf(instStateDead.String())
}

if err := syscall.Kill(pid, syscall.Signal(syscall.SIGHUP)); err != nil {
Expand Down
65 changes: 65 additions & 0 deletions cli/status/status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package status

import (
"fmt"
"strings"
"text/tabwriter"

"github.com/fatih/color"
"github.com/tarantool/tt/cli/process_utils"
"github.com/tarantool/tt/cli/running"
)

const (
// colorBytesNumber is the number of bytes added to the colorized string.
colorBytesNumber = 9

// padding is the padding between two columns of the table.
padding = 5
)

var header = []string{"INSTANCE", "STATUS", "PID"}

// Status writes the status as a table.
func Status(runningCtx running.RunningCtx) error {
instColWidth := 0
sb := strings.Builder{}
tw := tabwriter.NewWriter(&sb, 0, 1, padding, ' ', 0)

fmt.Fprintln(tw, strings.Join(header, "\t"))
for _, run := range runningCtx.Instances {
fullInstanceName := running.GetAppInstanceName(run)
procStatus := running.Status(&run)
if len(fullInstanceName) > instColWidth {
instColWidth = len(fullInstanceName)
}

fmt.Fprintf(tw, "%s\t%s\t", fullInstanceName,
procStatus.ColorSprint(procStatus.Status))
if procStatus.Code == process_utils.ProcessRunningCode {
fmt.Fprintf(tw, "%d", procStatus.PID)
}
fmt.Fprintf(tw, "\n")
}

if err := tw.Flush(); err != nil {
return err
}

rawOutput := sb.String()
rawHeader, rest, _ := strings.Cut(rawOutput, "\n")

// Calculating the position of the `status` end in the header.
statusOffset := instColWidth + padding + len(header[1])
fmt.Print(rawHeader[:statusOffset])

var toSkip int
if len(runningCtx.Instances) > 0 && !color.NoColor {
// We need to skip the spaces that appear
// as a result of using color bytes, if any.
toSkip = colorBytesNumber
}
fmt.Println(rawHeader[statusOffset+toSkip:])
fmt.Print(rest)
return nil
}
7 changes: 4 additions & 3 deletions test/integration/daemon/test_daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,8 @@ def test_daemon_http_requests(tt_cmd, tmpdir_with_cfg):
body = {"command_name": "status", "params": ["test_app"]}
response = requests.post(default_url, json=body)
assert response.status_code == 200
assert re.search(r"RUNNING. PID: \d+.", response.json()["res"])
status_info = utils.extract_status(response.json()["res"])
assert status_info["test_app"]["STATUS"] == "RUNNING"

body = {"command_name": "stop", "params": ["test_app"]}
response = requests.post(default_url, json=body)
Expand Down Expand Up @@ -286,8 +287,8 @@ def test_daemon_http_requests_with_cfg(tt_cmd, tmpdir_with_cfg):
body = {"command_name": "status", "params": ["test_app"]}
response = requests.post(url, json=body)
assert response.status_code == 200
assert re.search(r"RUNNING. PID: \d+.", response.json()["res"])

status_info = utils.extract_status(response.json()["res"])
assert status_info["test_app"]["STATUS"] == "RUNNING"
body = {"command_name": "stop", "params": ["test_app"]}
response = requests.post(url, json=body)
assert response.status_code == 200
Expand Down
48 changes: 32 additions & 16 deletions test/integration/running/test_running.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import yaml

from utils import (config_name, kill_child_process, log_path,
from utils import (config_name, extract_status, kill_child_process, log_path,
run_command_and_get_output, run_path, wait_file,
wait_instance_start, wait_instance_stop)

Expand Down Expand Up @@ -35,7 +35,8 @@ def test_running_base_functionality(tt_cmd, tmpdir_with_cfg):
status_cmd = [tt_cmd, "status", "test_app"]
status_rc, status_out = run_command_and_get_output(status_cmd, cwd=tmpdir)
assert status_rc == 0
assert re.search(r"RUNNING. PID: \d+.", status_out)
status_info = extract_status(status_out)
assert status_info["test_app"]["STATUS"] == "RUNNING"

# Stop the Instance.
stop_cmd = [tt_cmd, "stop", "test_app"]
Expand Down Expand Up @@ -72,7 +73,8 @@ def test_restart(tt_cmd, tmpdir_with_cfg):
status_cmd = [tt_cmd, "status", "test_app"]
status_rc, status_out = run_command_and_get_output(status_cmd, cwd=tmpdir)
assert status_rc == 0
assert re.search(r"RUNNING. PID: \d+.", status_out)
status_out = extract_status(status_out)
assert status_out["test_app"]["STATUS"] == "RUNNING"

# Restart the Instance.
restart_cmd = [tt_cmd, "restart", "-y", "test_app"]
Expand All @@ -98,7 +100,8 @@ def test_restart(tt_cmd, tmpdir_with_cfg):
status_cmd = [tt_cmd, "status", "test_app"]
status_rc, status_out = run_command_and_get_output(status_cmd, cwd=tmpdir)
assert status_rc == 0
assert re.search(r"RUNNING. PID: \d+.", status_out)
status_out = extract_status(status_out)
assert status_out["test_app"]["STATUS"] == "RUNNING"

# Stop the new Instance.
stop_cmd = [tt_cmd, "stop", "test_app"]
Expand Down Expand Up @@ -238,7 +241,10 @@ def test_running_base_functionality_working_dir_app(tt_cmd):
status_cmd = [tt_cmd, "status", "app"]
status_rc, status_out = run_command_and_get_output(status_cmd, cwd=test_app_path)
assert status_rc == 0
assert re.search(r"RUNNING. PID: \d+.", status_out)
status_out = extract_status(status_out)
assert status_out["app:router"]["STATUS"] == "RUNNING"
assert status_out["app:master"]["STATUS"] == "RUNNING"
assert status_out["app:replica"]["STATUS"] == "RUNNING"

# Stop the application.
stop_cmd = [tt_cmd, "stop", "app"]
Expand Down Expand Up @@ -286,7 +292,8 @@ def test_running_base_functionality_working_dir_app_no_app_name(tt_cmd):
status_cmd = [tt_cmd, "status"]
status_rc, status_out = run_command_and_get_output(status_cmd, cwd=test_app_path)
assert status_rc == 0
assert re.search(r"RUNNING. PID: \d+.", status_out)
status_out = extract_status(status_out)
assert status_out[f"app:{instName}"]["STATUS"] == "RUNNING"

# Stop the application.
stop_cmd = [tt_cmd, "stop"]
Expand Down Expand Up @@ -328,18 +335,20 @@ def test_running_instance_from_multi_inst_app(tt_cmd):
status_cmd = [tt_cmd, "status", "app:router"]
status_rc, status_out = run_command_and_get_output(status_cmd, cwd=test_app_path)
assert status_rc == 0
assert re.search(r"router: RUNNING. PID: \d+.", status_out)
status_out = extract_status(status_out)
assert status_out["app:router"]["STATUS"] == "RUNNING"

for inst in ["master", "replica"]:
status_cmd = [tt_cmd, "status", "app:" + inst]
status_rc, status_out = run_command_and_get_output(status_cmd, cwd=test_app_path)
assert status_rc == 0
assert re.search(inst + ": NOT RUNNING.", status_out)
status_out = extract_status(status_out)
assert status_out[f"app:{inst}"]["STATUS"] == "NOT RUNNING"

# Stop the Instance.
stop_cmd = [tt_cmd, "stop", "app:router"]
stop_rc, stop_out = run_command_and_get_output(stop_cmd, cwd=test_app_path)
assert status_rc == 0
assert stop_rc == 0
assert re.search(r"The Instance app:router \(PID = \d+\) has been terminated.", stop_out)

# Check that the process was terminated correctly.
Expand Down Expand Up @@ -415,9 +424,10 @@ def test_running_reread_config(tt_cmd, tmpdir):
status_cmd = [tt_cmd, "status", inst_name]
status_rc, status_out = run_command_and_get_output(status_cmd, cwd=tmpdir)
assert status_rc == 0
assert re.search(r"RUNNING. PID: \d+.", status_out)
status_out = extract_status(status_out)
assert status_out[inst_name]["STATUS"] == "RUNNING"

pid = int(''.join(filter(str.isdigit, status_out)))
pid = status_out[inst_name]["PID"]

# Wait for child process of instance to start.
# We need to wait because watchdog starts first and only after that
Expand All @@ -441,7 +451,8 @@ def test_running_reread_config(tt_cmd, tmpdir):
status_cmd = [tt_cmd, "status", inst_name]
status_rc, status_out = run_command_and_get_output(status_cmd, cwd=tmpdir)
assert status_rc == 0
assert re.search(r"RUNNING. PID: \d+.", status_out)
status_out = extract_status(status_out)
assert status_out[inst_name]["STATUS"] == "RUNNING"

with open(config_path, "w") as file:
yaml.dump({"tt": {"app": {"restart_on_failure": False,
Expand Down Expand Up @@ -502,8 +513,11 @@ def test_no_args_usage(tt_cmd):
status_cmd = [tt_cmd, "status"]
status_rc, status_out = run_command_and_get_output(status_cmd, cwd=test_app_path)
assert status_rc == 0
assert re.search(r"app1:(router|master|replica): RUNNING. PID: \d+.", status_out)
assert re.search(r"app2: RUNNING. PID: \d+.", status_out)
status_out = extract_status(status_out)
assert status_out['app1:router']["STATUS"] == "RUNNING"
assert status_out['app1:master']["STATUS"] == "RUNNING"
assert status_out['app1:replica']["STATUS"] == "RUNNING"
assert status_out['app2']["STATUS"] == "RUNNING"

status_cmd = [tt_cmd, "logrotate"]
status_rc, status_out = run_command_and_get_output(status_cmd, cwd=test_app_path)
Expand Down Expand Up @@ -553,7 +567,8 @@ def test_running_env_variables(tt_cmd, tmpdir_with_cfg):
status_cmd = [tt_cmd, "status", "test_env_app"]
status_rc, status_out = run_command_and_get_output(status_cmd, cwd=tmpdir)
assert status_rc == 0
assert re.search(r"RUNNING. PID: \d+.", status_out)
status_out = extract_status(status_out)
assert status_out["test_env_app"]["STATUS"] == "RUNNING"

# Stop the Instance.
stop_cmd = [tt_cmd, "stop", "test_env_app"]
Expand Down Expand Up @@ -610,7 +625,8 @@ def test_running_tarantoolctl_layout(tt_cmd, tmpdir):
status_cmd = [tt_cmd, "status", "test_app"]
status_rc, status_out = run_command_and_get_output(status_cmd, cwd=tmpdir)
assert status_rc == 0
assert re.search(r"RUNNING. PID: \d+.", status_out)
status_out = extract_status(status_out)
assert status_out["test_app"]["STATUS"] == "RUNNING"

# Stop the Instance.
stop_cmd = [tt_cmd, "stop", "test_app"]
Expand Down
17 changes: 17 additions & 0 deletions test/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,3 +322,20 @@ def find_port(port=8000):
return find_port(port=port + 1)
else:
return port


def extract_status(status_output):
result = {}
statuses = status_output.split("\n")
for i in range(1, len(statuses)-1):
summary = statuses[i]
fields = summary.split()
instance = fields[0]
info = {}
if fields[1] == "RUNNING":
info["STATUS"] = fields[1]
info["PID"] = int(fields[2])
else:
info["STATUS"] = " ".join(fields[1:])
result[instance] = info
return result

0 comments on commit e5b41ee

Please sign in to comment.