Skip to content

Commit 28efae4

Browse files
nghuiqinfacebook-github-bot
authored andcommitted
support AppStatus json output format
Summary: Enable json output format to show AppStatus What added: 1. Add `--json` subcommand for `status`, default is False 2. Parse both AppStatus and RoleStatus to dictionary format 3. json.dump dictionary in output 4. Add unit test Differential Revision: D73180040
1 parent bec9317 commit 28efae4

File tree

3 files changed

+79
-11
lines changed

3 files changed

+79
-11
lines changed

torchx/cli/cmd_status.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ def add_arguments(self, subparser: argparse.ArgumentParser) -> None:
4646
subparser.add_argument(
4747
"--roles", type=str, default="", help="comma separated roles to filter"
4848
)
49+
subparser.add_argument(
50+
"--json",
51+
action="store_true",
52+
help="output the status in JSON format",
53+
)
4954

5055
def run(self, args: argparse.Namespace) -> None:
5156
app_handle = args.app_handle
@@ -54,7 +59,7 @@ def run(self, args: argparse.Namespace) -> None:
5459
app_status = runner.status(app_handle)
5560
filter_roles = parse_list_arg(args.roles)
5661
if app_status:
57-
print(app_status.format(filter_roles))
62+
print(app_status.format(filter_roles, args.json))
5863
else:
5964
logger.error(
6065
f"AppDef: {app_id},"

torchx/specs/api.py

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,12 @@ class RoleStatus:
538538
role: str
539539
replicas: List[ReplicaStatus]
540540

541+
def to_dict(self) -> Dict[str, Any]:
542+
return {
543+
"role": self.role,
544+
"replicas": [asdict(replica) for replica in self.replicas],
545+
}
546+
541547

542548
@dataclass
543549
class AppStatus:
@@ -657,9 +663,20 @@ def _format_role_status(
657663
replica_data += self._format_replica_status(replica)
658664
return f"{replica_data}"
659665

666+
def _to_json(self, roles: List[RoleStatus]) -> Dict[str, Any]:
667+
return {
668+
"state": str(self.state),
669+
"num_restarts": self.num_restarts,
670+
"roles": [role_status.to_dict() for role_status in roles],
671+
"msg": self.msg,
672+
"structured_error_msg": self.structured_error_msg,
673+
"url": self.ui_url,
674+
}
675+
660676
def format(
661677
self,
662678
filter_roles: Optional[List[str]] = None,
679+
as_json: bool = False,
663680
) -> str:
664681
"""
665682
Format logs for app status. The app status include:
@@ -669,19 +686,27 @@ def format(
669686
4. Msg: Arbitrary text message the scheduler returned.
670687
5. Structured Error Msg: Json response error msg.
671688
6. UI URL: Application URL
689+
690+
Args:
691+
filter_roles: Optional list of role names to filter by
692+
as_json: Show the output in json format
672693
"""
673694
roles_data = ""
674695
roles = self._get_role_statuses(self.roles, filter_roles)
675-
for role_status in roles:
676-
roles_data += self._format_role_status(role_status)
677-
return Template(_APP_STATUS_FORMAT_TEMPLATE).substitute(
678-
state=self.state,
679-
num_restarts=self.num_restarts,
680-
roles=roles_data,
681-
msg=self.msg,
682-
structured_error_msg=self.structured_error_msg,
683-
url=self.ui_url,
684-
)
696+
697+
if as_json:
698+
return json.dumps(self._to_json(roles), indent=4)
699+
else:
700+
for role_status in roles:
701+
roles_data += self._format_role_status(role_status)
702+
return Template(_APP_STATUS_FORMAT_TEMPLATE).substitute(
703+
state=self.state,
704+
num_restarts=self.num_restarts,
705+
roles=roles_data,
706+
msg=self.msg,
707+
structured_error_msg=self.structured_error_msg,
708+
url=self.ui_url,
709+
)
685710

686711

687712
class AppStatusError(Exception):

torchx/specs/test/api_test.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,44 @@ def test_format_app_status(self) -> None:
176176
# Split and compare to aviod AssertionError.
177177
self.assertEqual(expected_message.split(), actual_message.split())
178178

179+
def test_format_app_status_in_json(self) -> None:
180+
app_status = self._get_test_app_status()
181+
actual_message = app_status.format(as_json=True)
182+
error_msg = '{\\"message\\":{\\"message\\":\\"error\\",\\"errorCode\\":-1,\\"extraInfo\\":{\\"timestamp\\":1293182}}}'
183+
expected_message = (
184+
"""{
185+
"state": "RUNNING",
186+
"num_restarts": 0,
187+
"roles": [
188+
{
189+
"role": "worker",
190+
"replicas": [
191+
{
192+
"id": 0,
193+
"state": 5,
194+
"role": "worker",
195+
"hostname": "localhost",
196+
"structured_error_msg": "%s"
197+
},
198+
{
199+
"id": 1,
200+
"state": 3,
201+
"role": "worker",
202+
"hostname": "localhost",
203+
"structured_error_msg": "<NONE>"
204+
}
205+
]
206+
}
207+
],
208+
"msg": "",
209+
"structured_error_msg": "<NONE>",
210+
"url": null
211+
}"""
212+
% error_msg
213+
)
214+
print(expected_message)
215+
self.assertEqual(expected_message, actual_message)
216+
179217

180218
class ResourceTest(unittest.TestCase):
181219
def test_copy_resource(self) -> None:

0 commit comments

Comments
 (0)