ttnn-visualizer 0.44.0__py3-none-any.whl → 0.45.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,6 @@
4
4
  import csv
5
5
  import json
6
6
  import os
7
- import subprocess
8
7
  import tempfile
9
8
  from io import StringIO
10
9
  from pathlib import Path
@@ -13,62 +12,10 @@ from typing import Dict, List, Optional, Union
13
12
  import pandas as pd
14
13
  import zstd
15
14
  from tt_perf_report import perf_report
16
- from ttnn_visualizer.exceptions import (
17
- AuthenticationException,
18
- DataFormatError,
19
- NoValidConnectionsError,
20
- SSHException,
21
- )
15
+ from ttnn_visualizer.exceptions import DataFormatError
22
16
  from ttnn_visualizer.models import Instance, RemoteConnection
23
17
 
24
18
 
25
- def handle_ssh_subprocess_error(
26
- e: subprocess.CalledProcessError, remote_connection: RemoteConnection
27
- ):
28
- """
29
- Convert subprocess SSH errors to appropriate SSH exceptions.
30
-
31
- :param e: The subprocess.CalledProcessError
32
- :param remote_connection: The RemoteConnection object for context
33
- :raises: SSHException, AuthenticationException, or NoValidConnectionsError
34
- """
35
- stderr = e.stderr.lower() if e.stderr else ""
36
-
37
- # Check for authentication failures
38
- if any(
39
- auth_err in stderr
40
- for auth_err in [
41
- "permission denied",
42
- "authentication failed",
43
- "publickey",
44
- "password",
45
- "host key verification failed",
46
- ]
47
- ):
48
- raise AuthenticationException(f"SSH authentication failed: {e.stderr}")
49
-
50
- # Check for connection failures
51
- elif any(
52
- conn_err in stderr
53
- for conn_err in [
54
- "connection refused",
55
- "network is unreachable",
56
- "no route to host",
57
- "name or service not known",
58
- "connection timed out",
59
- ]
60
- ):
61
- raise NoValidConnectionsError(f"SSH connection failed: {e.stderr}")
62
-
63
- # Check for general SSH protocol errors
64
- elif "ssh:" in stderr or "protocol" in stderr:
65
- raise SSHException(f"SSH protocol error: {e.stderr}")
66
-
67
- # Default to generic SSH exception
68
- else:
69
- raise SSHException(f"SSH command failed: {e.stderr}")
70
-
71
-
72
19
  class LocalCSVQueryRunner:
73
20
  def __init__(self, file_path: str, offset: int = 0):
74
21
  self.file_path = file_path
@@ -14,10 +14,12 @@ class RemoteConnectionException(Exception):
14
14
  message,
15
15
  status: ConnectionTestStates,
16
16
  http_status_code: Optional[HTTPStatus] = None,
17
+ detail: Optional[str] = None,
17
18
  ):
18
19
  super().__init__(message)
19
20
  self.message = message
20
21
  self.status = status
22
+ self.detail = detail
21
23
  self._http_status_code = http_status_code
22
24
 
23
25
  @property
@@ -37,12 +39,16 @@ class AuthenticationFailedException(RemoteConnectionException):
37
39
  """Exception for SSH authentication failures that should return HTTP 422"""
38
40
 
39
41
  def __init__(
40
- self, message, status: ConnectionTestStates = ConnectionTestStates.FAILED
42
+ self,
43
+ message,
44
+ status: ConnectionTestStates = ConnectionTestStates.FAILED,
45
+ detail: Optional[str] = None,
41
46
  ):
42
47
  super().__init__(
43
48
  message=message,
44
49
  status=status,
45
50
  http_status_code=HTTPStatus.UNPROCESSABLE_ENTITY, # 422
51
+ detail=detail,
46
52
  )
47
53
 
48
54
 
ttnn_visualizer/models.py CHANGED
@@ -174,6 +174,7 @@ class RemoteConnection(SerializeableModel):
174
174
  class StatusMessage(SerializeableModel):
175
175
  status: ConnectionTestStates
176
176
  message: str
177
+ detail: Optional[str] = None
177
178
 
178
179
 
179
180
  class ActiveReports(SerializeableModel):
@@ -3,100 +3,24 @@
3
3
  # SPDX-FileCopyrightText: © 2025 Tenstorrent AI ULC
4
4
 
5
5
  import re
6
- import subprocess
7
6
 
8
7
  from ttnn_visualizer.decorators import remote_exception_handler
9
8
  from ttnn_visualizer.enums import ConnectionTestStates
10
- from ttnn_visualizer.exceptions import (
11
- AuthenticationException,
12
- NoValidConnectionsError,
13
- RemoteSqliteException,
14
- SSHException,
15
- )
9
+ from ttnn_visualizer.exceptions import RemoteSqliteException, SSHException
16
10
  from ttnn_visualizer.models import RemoteConnection
17
-
18
-
19
- def handle_ssh_subprocess_error(
20
- e: subprocess.CalledProcessError, remote_connection: RemoteConnection
21
- ):
22
- """
23
- Convert subprocess SSH errors to appropriate SSH exceptions.
24
-
25
- :param e: The subprocess.CalledProcessError
26
- :param remote_connection: The RemoteConnection object for context
27
- :raises: SSHException, AuthenticationException, or NoValidConnectionsError
28
- """
29
- stderr = e.stderr.lower() if e.stderr else ""
30
-
31
- # Check for authentication failures
32
- if any(
33
- auth_err in stderr
34
- for auth_err in [
35
- "permission denied",
36
- "authentication failed",
37
- "publickey",
38
- "password",
39
- "host key verification failed",
40
- ]
41
- ):
42
- raise AuthenticationException(f"SSH authentication failed: {e.stderr}")
43
-
44
- # Check for connection failures
45
- elif any(
46
- conn_err in stderr
47
- for conn_err in [
48
- "connection refused",
49
- "network is unreachable",
50
- "no route to host",
51
- "name or service not known",
52
- "connection timed out",
53
- ]
54
- ):
55
- raise NoValidConnectionsError(f"SSH connection failed: {e.stderr}")
56
-
57
- # Check for general SSH protocol errors
58
- elif "ssh:" in stderr or "protocol" in stderr:
59
- raise SSHException(f"SSH protocol error: {e.stderr}")
60
-
61
- # Default to generic SSH exception
62
- else:
63
- raise SSHException(f"SSH command failed: {e.stderr}")
64
-
11
+ from ttnn_visualizer.ssh_client import SSHClient
65
12
 
66
13
  MINIMUM_SQLITE_VERSION = "3.38.0"
67
14
 
68
15
 
69
16
  def _execute_ssh_command(remote_connection: RemoteConnection, command: str) -> str:
70
17
  """Execute an SSH command and return the output."""
71
- ssh_cmd = ["ssh", "-o", "PasswordAuthentication=no"]
72
-
73
- # Handle non-standard SSH port
74
- if remote_connection.port != 22:
75
- ssh_cmd.extend(["-p", str(remote_connection.port)])
76
-
77
- ssh_cmd.extend([f"{remote_connection.username}@{remote_connection.host}", command])
78
-
18
+ ssh_client = SSHClient(remote_connection)
79
19
  try:
80
- result = subprocess.run(
81
- ssh_cmd, capture_output=True, text=True, check=True, timeout=30
82
- )
83
- return result.stdout
84
- except subprocess.CalledProcessError as e:
85
- if e.returncode == 255: # SSH protocol errors
86
- handle_ssh_subprocess_error(e, remote_connection)
87
- # This line should never be reached as handle_ssh_subprocess_error raises an exception
88
- raise RemoteSqliteException(
89
- message=f"SSH command failed: {e.stderr}",
90
- status=ConnectionTestStates.FAILED,
91
- )
92
- else:
93
- raise RemoteSqliteException(
94
- message=f"SSH command failed: {e.stderr}",
95
- status=ConnectionTestStates.FAILED,
96
- )
97
- except subprocess.TimeoutExpired:
20
+ return ssh_client.execute_command(command, timeout=30)
21
+ except SSHException as e:
98
22
  raise RemoteSqliteException(
99
- message=f"SSH command timed out: {command}",
23
+ message=str(e),
100
24
  status=ConnectionTestStates.FAILED,
101
25
  )
102
26
 
@@ -24,6 +24,7 @@ from ttnn_visualizer.exceptions import (
24
24
  )
25
25
  from ttnn_visualizer.models import RemoteConnection, RemoteReportFolder
26
26
  from ttnn_visualizer.sockets import FileProgress, FileStatus, emit_file_status
27
+ from ttnn_visualizer.ssh_client import SSHClient
27
28
  from ttnn_visualizer.utils import update_last_synced
28
29
 
29
30
  logger = logging.getLogger(__name__)
@@ -33,53 +34,6 @@ TEST_PROFILER_FILE = "profile_log_device.csv"
33
34
  REPORT_DATA_DIRECTORY = Path(__file__).parent.absolute().joinpath("data")
34
35
 
35
36
 
36
- def handle_ssh_subprocess_error(
37
- e: subprocess.CalledProcessError, remote_connection: RemoteConnection
38
- ):
39
- """
40
- Convert subprocess SSH errors to appropriate SSH exceptions.
41
-
42
- :param e: The subprocess.CalledProcessError
43
- :param remote_connection: The RemoteConnection object for context
44
- :raises: SSHException, AuthenticationException, or NoValidConnectionsError
45
- """
46
- stderr = e.stderr.lower() if e.stderr else ""
47
-
48
- # Check for authentication failures
49
- if any(
50
- auth_err in stderr
51
- for auth_err in [
52
- "permission denied",
53
- "authentication failed",
54
- "publickey",
55
- "password",
56
- "host key verification failed",
57
- ]
58
- ):
59
- raise AuthenticationException(f"SSH authentication failed: {e.stderr}")
60
-
61
- # Check for connection failures
62
- elif any(
63
- conn_err in stderr
64
- for conn_err in [
65
- "connection refused",
66
- "network is unreachable",
67
- "no route to host",
68
- "name or service not known",
69
- "connection timed out",
70
- ]
71
- ):
72
- raise NoValidConnectionsError(f"SSH connection failed: {e.stderr}")
73
-
74
- # Check for general SSH protocol errors
75
- elif "ssh:" in stderr or "protocol" in stderr:
76
- raise SSHException(f"SSH protocol error: {e.stderr}")
77
-
78
- # Default to generic SSH exception
79
- else:
80
- raise SSHException(f"SSH command failed: {e.stderr}")
81
-
82
-
83
37
  def start_background_task(task, *args):
84
38
  with current_app.app_context():
85
39
  if current_app.config["USE_WEBSOCKETS"]:
@@ -663,36 +617,8 @@ def read_remote_file(
663
617
  else:
664
618
  path = Path(remote_connection.profilerPath)
665
619
 
666
- logger.info(f"Reading remote file {path}")
667
-
668
- # Build SSH command to read the file
669
- ssh_cmd = ["ssh", "-o", "PasswordAuthentication=no"]
670
-
671
- # Handle non-standard SSH port
672
- if remote_connection.port != 22:
673
- ssh_cmd.extend(["-p", str(remote_connection.port)])
674
-
675
- ssh_cmd.extend(
676
- [f"{remote_connection.username}@{remote_connection.host}", f"cat '{path}'"]
677
- )
678
-
679
- try:
680
- result = subprocess.run(ssh_cmd, capture_output=True, check=True, timeout=30)
681
- return result.stdout
682
- except subprocess.CalledProcessError as e:
683
- if e.returncode == 255: # SSH protocol errors
684
- handle_ssh_subprocess_error(e, remote_connection)
685
- return None
686
- else:
687
- # File not found or other command error
688
- logger.error(f"File not found or cannot be read: {path}")
689
- return None
690
- except subprocess.TimeoutExpired:
691
- logger.error(f"Timeout reading remote file: {path}")
692
- return None
693
- except Exception as e:
694
- logger.error(f"Error reading remote file {path}: {e}")
695
- return None
620
+ ssh_client = SSHClient(remote_connection)
621
+ return ssh_client.read_file(path, timeout=30)
696
622
 
697
623
 
698
624
  @remote_exception_handler
@@ -713,24 +639,11 @@ def check_remote_path_exists(remote_connection: RemoteConnection, path_key: str)
713
639
  """Check if a remote path exists using SSH test command."""
714
640
  path = getattr(remote_connection, path_key)
715
641
 
716
- # Build SSH command to test if path exists
717
- ssh_cmd = ["ssh", "-o", "PasswordAuthentication=no"]
718
-
719
- # Handle non-standard SSH port
720
- if remote_connection.port != 22:
721
- ssh_cmd.extend(["-p", str(remote_connection.port)])
722
-
723
- ssh_cmd.extend(
724
- [f"{remote_connection.username}@{remote_connection.host}", f"test -d '{path}'"]
725
- )
642
+ ssh_client = SSHClient(remote_connection)
726
643
 
727
644
  try:
728
- result = subprocess.run(ssh_cmd, capture_output=True, check=True, timeout=10)
729
- # If command succeeds, directory exists
730
- return True
731
- except subprocess.CalledProcessError as e:
732
- if e.returncode == 255: # SSH protocol errors
733
- handle_ssh_subprocess_error(e, remote_connection)
645
+ if ssh_client.check_path_exists(path, timeout=10):
646
+ return True
734
647
  else:
735
648
  # Directory does not exist or is inaccessible
736
649
  if path_key == "performancePath":
@@ -742,10 +655,10 @@ def check_remote_path_exists(remote_connection: RemoteConnection, path_key: str)
742
655
  raise RemoteConnectionException(
743
656
  message=message, status=ConnectionTestStates.FAILED
744
657
  )
745
- except subprocess.TimeoutExpired:
746
- logger.error(f"Timeout checking remote path: {path}")
658
+ except SSHException as e:
659
+ logger.error(f"Error checking remote path: {path}")
747
660
  raise RemoteConnectionException(
748
- message=f"Timeout checking remote path: {path}",
661
+ message=f"Error checking remote path: {path}: {str(e)}",
749
662
  status=ConnectionTestStates.FAILED,
750
663
  )
751
664
 
@@ -902,11 +815,11 @@ def sync_remote_profiler_folders(
902
815
  def sync_remote_performance_folders(
903
816
  remote_connection: RemoteConnection,
904
817
  path_prefix: str,
905
- profile: RemoteReportFolder,
818
+ performance: RemoteReportFolder,
906
819
  exclude_patterns: Optional[List[str]] = None,
907
820
  sid=None,
908
821
  ):
909
- remote_folder_path = profile.remotePath
822
+ remote_folder_path = performance.remotePath
910
823
  profile_folder = Path(remote_folder_path).name
911
824
  destination_dir = Path(
912
825
  REPORT_DATA_DIRECTORY,