ttnn-visualizer 0.41.0__py3-none-any.whl → 0.43.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.
Files changed (39) hide show
  1. ttnn_visualizer/__init__.py +0 -1
  2. ttnn_visualizer/app.py +15 -4
  3. ttnn_visualizer/csv_queries.py +150 -40
  4. ttnn_visualizer/decorators.py +42 -16
  5. ttnn_visualizer/exceptions.py +45 -1
  6. ttnn_visualizer/file_uploads.py +1 -0
  7. ttnn_visualizer/instances.py +42 -15
  8. ttnn_visualizer/models.py +12 -7
  9. ttnn_visualizer/queries.py +3 -109
  10. ttnn_visualizer/remote_sqlite_setup.py +104 -19
  11. ttnn_visualizer/requirements.txt +2 -3
  12. ttnn_visualizer/serializers.py +1 -0
  13. ttnn_visualizer/settings.py +9 -5
  14. ttnn_visualizer/sftp_operations.py +657 -220
  15. ttnn_visualizer/sockets.py +9 -3
  16. ttnn_visualizer/static/assets/{allPaths-4_pFqSAW.js → allPaths-BQN_j7ek.js} +1 -1
  17. ttnn_visualizer/static/assets/{allPathsLoader-CpLPTLlt.js → allPathsLoader-BvkkQ77q.js} +2 -2
  18. ttnn_visualizer/static/assets/index-B-fsa5Ru.js +1 -0
  19. ttnn_visualizer/static/assets/{index-DFVwehlj.js → index-Bng0kcmi.js} +214 -214
  20. ttnn_visualizer/static/assets/{index-C1rJBrMl.css → index-C-t6jBt9.css} +1 -1
  21. ttnn_visualizer/static/assets/index-DLOviMB1.js +1 -0
  22. ttnn_visualizer/static/assets/{splitPathsBySizeLoader-D-RvsTqO.js → splitPathsBySizeLoader-Cl0NRdfL.js} +1 -1
  23. ttnn_visualizer/static/index.html +2 -2
  24. ttnn_visualizer/tests/__init__.py +0 -1
  25. ttnn_visualizer/tests/test_queries.py +0 -69
  26. ttnn_visualizer/tests/test_serializers.py +2 -2
  27. ttnn_visualizer/utils.py +7 -3
  28. ttnn_visualizer/views.py +315 -52
  29. {ttnn_visualizer-0.41.0.dist-info → ttnn_visualizer-0.43.0.dist-info}/LICENSE +0 -1
  30. {ttnn_visualizer-0.41.0.dist-info → ttnn_visualizer-0.43.0.dist-info}/METADATA +6 -3
  31. ttnn_visualizer-0.43.0.dist-info/RECORD +45 -0
  32. ttnn_visualizer/ssh_client.py +0 -85
  33. ttnn_visualizer/static/assets/index-BKzgFDAn.js +0 -1
  34. ttnn_visualizer/static/assets/index-BvSuWPlB.js +0 -1
  35. ttnn_visualizer-0.41.0.dist-info/RECORD +0 -46
  36. {ttnn_visualizer-0.41.0.dist-info → ttnn_visualizer-0.43.0.dist-info}/LICENSE_understanding.txt +0 -0
  37. {ttnn_visualizer-0.41.0.dist-info → ttnn_visualizer-0.43.0.dist-info}/WHEEL +0 -0
  38. {ttnn_visualizer-0.41.0.dist-info → ttnn_visualizer-0.43.0.dist-info}/entry_points.txt +0 -0
  39. {ttnn_visualizer-0.41.0.dist-info → ttnn_visualizer-0.43.0.dist-info}/top_level.txt +0 -0
ttnn_visualizer/models.py CHANGED
@@ -119,6 +119,7 @@ class Tensor(SerializeableDataclass):
119
119
  def __post_init__(self):
120
120
  self.memory_config = parse_memory_config(self.memory_config)
121
121
 
122
+
122
123
  @dataclasses.dataclass
123
124
  class InputTensor(SerializeableDataclass):
124
125
  operation_id: int
@@ -246,19 +247,21 @@ class InstanceTable(db.Model):
246
247
  "remote_performance_folder": self.remote_performance_folder,
247
248
  "profiler_path": self.profiler_path,
248
249
  "performance_path": self.performance_path,
249
- "npe_path": self.npe_path
250
+ "npe_path": self.npe_path,
250
251
  }
251
252
 
252
253
  def to_pydantic(self) -> Instance:
253
254
  return Instance(
254
255
  instance_id=str(self.instance_id),
255
- profiler_path=str(self.profiler_path) if self.profiler_path is not None else None,
256
- performance_path=(
257
- str(self.performance_path) if self.performance_path is not None else None
256
+ profiler_path=(
257
+ str(self.profiler_path) if self.profiler_path is not None else None
258
258
  ),
259
- npe_path=(
260
- str(self.npe_path) if self.npe_path is not None else None
259
+ performance_path=(
260
+ str(self.performance_path)
261
+ if self.performance_path is not None
262
+ else None
261
263
  ),
264
+ npe_path=(str(self.npe_path) if self.npe_path is not None else None),
262
265
  active_report=(
263
266
  (ActiveReports(**self.active_report) if self.active_report else None)
264
267
  if isinstance(self.active_report, dict)
@@ -270,7 +273,9 @@ class InstanceTable(db.Model):
270
273
  else None
271
274
  ),
272
275
  remote_profiler_folder=(
273
- RemoteReportFolder.model_validate(self.remote_profiler_folder, strict=False)
276
+ RemoteReportFolder.model_validate(
277
+ self.remote_profiler_folder, strict=False
278
+ )
274
279
  if self.remote_profiler_folder is not None
275
280
  else None
276
281
  ),
@@ -2,7 +2,6 @@
2
2
  #
3
3
  # SPDX-FileCopyrightText: © 2025 Tenstorrent AI ULC
4
4
 
5
- import json
6
5
  from typing import Generator, Dict, Any, Union
7
6
 
8
7
  from ttnn_visualizer.exceptions import (
@@ -23,11 +22,9 @@ from ttnn_visualizer.models import (
23
22
  ProducersConsumers,
24
23
  TensorComparisonRecord,
25
24
  )
26
- from ttnn_visualizer.ssh_client import get_client
27
25
  import sqlite3
28
26
  from typing import List, Optional
29
27
  from pathlib import Path
30
- import paramiko
31
28
 
32
29
 
33
30
  class LocalQueryRunner:
@@ -63,111 +60,10 @@ class LocalQueryRunner:
63
60
  self.connection.close()
64
61
 
65
62
 
66
- class RemoteQueryRunner:
67
- column_delimiter = "|||"
68
-
69
- def __init__(self, instance: Instance):
70
- self.instance = instance
71
- self._validate_instance()
72
- self.ssh_client = self._get_ssh_client(self.instance.remote_connection)
73
- self.sqlite_binary = self.instance.remote_connection.sqliteBinaryPath
74
- self.remote_db_path = str(
75
- Path(self.instance.remote_profiler_folder.remotePath, "db.sqlite")
76
- )
77
-
78
- def _validate_instance(self):
79
- """
80
- Validate that the instance has all required remote connection attributes.
81
- """
82
- if (
83
- not self.instance.remote_connection
84
- or not self.instance.remote_connection.sqliteBinaryPath
85
- or not self.instance.remote_profiler_folder
86
- or not self.instance.remote_profiler_folder.remotePath
87
- ):
88
- raise ValueError(
89
- "Remote connections require remote path and sqliteBinaryPath"
90
- )
91
-
92
- def _get_ssh_client(self, remote_connection) -> paramiko.SSHClient:
93
- """
94
- Retrieve the SSH client for the given remote connection.
95
- """
96
- return get_client(remote_connection=remote_connection)
97
-
98
- def _format_query(self, query: str, params: Optional[List] = None) -> str:
99
- """
100
- Format the query by replacing placeholders with properly quoted parameters.
101
- """
102
- if not params:
103
- return query
104
-
105
- formatted_params = [
106
- f"'{param}'" if isinstance(param, str) else str(param) for param in params
107
- ]
108
- return query.replace("?", "{}").format(*formatted_params)
109
-
110
- def _build_command(self, formatted_query: str) -> str:
111
- """
112
- Build the remote SQLite command.
113
- """
114
- return f'{self.sqlite_binary} {self.remote_db_path} "{formatted_query}" -json'
115
-
116
- def _execute_ssh_command(self, command: str) -> tuple:
117
- """
118
- Execute the SSH command and return the standard output and error.
119
- """
120
- stdin, stdout, stderr = self.ssh_client.exec_command(command)
121
- output = stdout.read().decode("utf-8").strip()
122
- error_output = stderr.read().decode("utf-8").strip()
123
- return output, error_output
124
-
125
- def _parse_output(self, output: str, command: str) -> List:
126
- """
127
- Parse the output from the SQLite command. Attempt JSON parsing first,
128
- then fall back to line-based parsing.
129
- """
130
- if not output.strip():
131
- return []
132
-
133
- try:
134
- rows = json.loads(output)
135
- return [tuple(row.values()) for row in rows]
136
- except json.JSONDecodeError:
137
- print(
138
- f"Output is not valid JSON, attempting manual parsing.\nCommand: {command}"
139
- )
140
- return [tuple(line.split("|")) for line in output.splitlines()]
141
-
142
- def execute_query(self, query: str, params: Optional[List] = None) -> List:
143
- """
144
- Execute a remote SQLite query using the instance's SSH client.
145
- """
146
- self._validate_instance()
147
- formatted_query = self._format_query(query, params)
148
- command = self._build_command(formatted_query)
149
- output, error_output = self._execute_ssh_command(command)
150
-
151
- if error_output:
152
- raise RuntimeError(
153
- f"Error executing query remotely: {error_output}\nCommand: {command}"
154
- )
155
-
156
- return self._parse_output(output, command)
157
-
158
- def close(self):
159
- """
160
- Close the SSH connection.
161
- """
162
- if self.ssh_client:
163
- self.ssh_client.close()
164
-
165
-
166
63
  class DatabaseQueries:
167
64
 
168
65
  instance: Optional[Instance] = None
169
- ssh_client = None
170
- query_runner: LocalQueryRunner | RemoteQueryRunner
66
+ query_runner: LocalQueryRunner
171
67
 
172
68
  def __init__(self, instance: Optional[Instance] = None, connection=None):
173
69
  self.instance = instance
@@ -181,7 +77,7 @@ class DatabaseQueries:
181
77
  )
182
78
  remote_connection = instance.remote_connection if instance else None
183
79
  if remote_connection and remote_connection.useRemoteQuerying:
184
- self.query_runner = RemoteQueryRunner(instance=instance)
80
+ raise NotImplementedError("Remote querying is not implemented yet")
185
81
  else:
186
82
  self.query_runner = LocalQueryRunner(instance=instance)
187
83
 
@@ -382,7 +278,5 @@ class DatabaseQueries:
382
278
  return self
383
279
 
384
280
  def __exit__(self, exc_type, exc_value, traceback):
385
- if isinstance(self.query_runner, RemoteQueryRunner):
386
- self.query_runner.close()
387
- elif isinstance(self.query_runner, LocalQueryRunner):
281
+ if isinstance(self.query_runner, LocalQueryRunner):
388
282
  self.query_runner.close()
@@ -3,29 +3,116 @@
3
3
  # SPDX-FileCopyrightText: © 2025 Tenstorrent AI ULC
4
4
 
5
5
  import re
6
+ import subprocess
6
7
 
7
8
  from ttnn_visualizer.decorators import remote_exception_handler
8
9
  from ttnn_visualizer.enums import ConnectionTestStates
9
- from ttnn_visualizer.exceptions import RemoteSqliteException
10
+ from ttnn_visualizer.exceptions import (
11
+ RemoteSqliteException,
12
+ SSHException,
13
+ AuthenticationException,
14
+ NoValidConnectionsError,
15
+ )
10
16
  from ttnn_visualizer.models import RemoteConnection
11
- from ttnn_visualizer.ssh_client import get_client
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
+
12
65
 
13
66
  MINIMUM_SQLITE_VERSION = "3.38.0"
14
67
 
15
68
 
69
+ def _execute_ssh_command(remote_connection: RemoteConnection, command: str) -> str:
70
+ """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
+
79
+ 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:
98
+ raise RemoteSqliteException(
99
+ message=f"SSH command timed out: {command}",
100
+ status=ConnectionTestStates.FAILED,
101
+ )
102
+
103
+
16
104
  def find_sqlite_binary(connection):
17
105
  """Check if SQLite is installed on the remote machine and return its path."""
18
- ssh_client = get_client(connection)
19
106
  try:
20
- stdin, stdout, stderr = ssh_client.exec_command("which sqlite3")
21
- binary_path = stdout.read().decode().strip()
22
- error = stderr.read().decode().strip()
107
+ output = _execute_ssh_command(connection, "which sqlite3")
108
+ binary_path = output.strip()
23
109
  if binary_path:
24
110
  print(f"SQLite binary found at: {binary_path}")
25
111
  return binary_path
26
- elif error:
27
- print(f"Error checking SQLite binary: {error}")
28
112
  return None
113
+ except RemoteSqliteException:
114
+ # Re-raise RemoteSqliteException as-is
115
+ raise
29
116
  except Exception as e:
30
117
  raise RemoteSqliteException(
31
118
  message=f"Error finding SQLite binary: {str(e)}",
@@ -33,17 +120,13 @@ def find_sqlite_binary(connection):
33
120
  )
34
121
 
35
122
 
36
- def is_sqlite_executable(ssh_client, binary_path):
123
+ def is_sqlite_executable(remote_connection: RemoteConnection, binary_path):
37
124
  """Check if the SQLite binary is executable by trying to run it."""
38
125
  try:
39
- stdin, stdout, stderr = ssh_client.exec_command(f"{binary_path} --version")
40
- output = stdout.read().decode().strip()
41
- error = stderr.read().decode().strip()
42
- stdout.channel.recv_exit_status()
43
- if error:
44
- raise Exception(f"Error while trying to run SQLite binary: {error}")
45
-
46
- version = get_sqlite_version(output)
126
+ output = _execute_ssh_command(remote_connection, f"{binary_path} --version")
127
+ version_output = output.strip()
128
+
129
+ version = get_sqlite_version(version_output)
47
130
  if not is_version_at_least(version, MINIMUM_SQLITE_VERSION):
48
131
  raise Exception(
49
132
  f"SQLite version {version} is below the required minimum of {MINIMUM_SQLITE_VERSION}."
@@ -52,6 +135,9 @@ def is_sqlite_executable(ssh_client, binary_path):
52
135
  print(f"SQLite binary at {binary_path} is executable. Version: {version}")
53
136
  return True
54
137
 
138
+ except RemoteSqliteException:
139
+ # Re-raise RemoteSqliteException as-is
140
+ raise
55
141
  except Exception as e:
56
142
  raise Exception(f"Error checking SQLite executability: {str(e)}")
57
143
 
@@ -76,8 +162,7 @@ def is_version_at_least(version, minimum_version):
76
162
  @remote_exception_handler
77
163
  def check_sqlite_path(remote_connection: RemoteConnection):
78
164
  try:
79
- client = get_client(remote_connection)
80
- is_sqlite_executable(client, remote_connection.sqliteBinaryPath)
165
+ is_sqlite_executable(remote_connection, remote_connection.sqliteBinaryPath)
81
166
  except Exception as e:
82
167
  raise RemoteSqliteException(message=str(e), status=ConnectionTestStates.FAILED)
83
168
 
@@ -1,7 +1,6 @@
1
1
  Flask==3.1.1
2
2
  gunicorn~=22.0.0
3
3
  uvicorn==0.30.1
4
- paramiko~=3.4.0
5
4
  flask_cors==4.0.1
6
5
  pydantic==2.7.3
7
6
  pydantic_core==2.18.4
@@ -16,9 +15,9 @@ wheel
16
15
  build
17
16
  PyYAML==6.0.2
18
17
  python-dotenv==1.0.1
19
- tt-perf-report==1.0.6
18
+ tt-perf-report==1.0.7
20
19
  zstd==1.5.7.0
21
20
 
22
21
  # Dev dependencies
23
22
  mypy
24
- types-paramiko
23
+ black==25.1.0
@@ -8,6 +8,7 @@ from typing import List
8
8
 
9
9
  from ttnn_visualizer.models import BufferType, Operation, TensorComparisonRecord
10
10
 
11
+
11
12
  def serialize_operations(
12
13
  inputs,
13
14
  operation_arguments,
@@ -10,6 +10,7 @@ from ttnn_visualizer.utils import str_to_bool
10
10
 
11
11
  load_dotenv()
12
12
 
13
+
13
14
  class DefaultConfig(object):
14
15
  # General Settings
15
16
  SECRET_KEY = os.getenv("SECRET_KEY", "90909")
@@ -19,17 +20,20 @@ class DefaultConfig(object):
19
20
  SERVER_MODE = str_to_bool(os.getenv("SERVER_MODE", "false"))
20
21
  MALWARE_SCANNER = os.getenv("MALWARE_SCANNER")
21
22
  ALLOWED_ORIGINS = [
22
- o for o in os.getenv("ALLOWED_ORIGINS", "http://localhost:5173,http://localhost:8000").split(",")
23
+ o
24
+ for o in os.getenv(
25
+ "ALLOWED_ORIGINS", "http://localhost:5173,http://localhost:8000"
26
+ ).split(",")
23
27
  if o
24
28
  ]
25
29
  BASE_PATH = os.getenv("BASE_PATH", "/")
26
- MAX_CONTENT_LENGTH = (
27
- None if not (v := os.getenv("MAX_CONTENT_LENGTH")) else int(v)
28
- )
30
+ MAX_CONTENT_LENGTH = None if not (v := os.getenv("MAX_CONTENT_LENGTH")) else int(v)
29
31
 
30
32
  # Path Settings
31
33
  DB_VERSION = "0.29.0" # App version when DB schema last changed
32
- REPORT_DATA_DIRECTORY = os.getenv("REPORT_DATA_DIRECTORY", Path(__file__).parent.absolute().joinpath("data"))
34
+ REPORT_DATA_DIRECTORY = os.getenv(
35
+ "REPORT_DATA_DIRECTORY", Path(__file__).parent.absolute().joinpath("data")
36
+ )
33
37
  LOCAL_DATA_DIRECTORY = Path(REPORT_DATA_DIRECTORY).joinpath("local")
34
38
  REMOTE_DATA_DIRECTORY = Path(REPORT_DATA_DIRECTORY).joinpath("remote")
35
39
  PROFILER_DIRECTORY_NAME = "profiler-reports"