ttnn-visualizer 0.44.1__py3-none-any.whl → 0.46.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.
- ttnn_visualizer/csv_queries.py +1 -54
- ttnn_visualizer/decorators.py +3 -3
- ttnn_visualizer/exceptions.py +7 -1
- ttnn_visualizer/models.py +1 -0
- ttnn_visualizer/remote_sqlite_setup.py +6 -82
- ttnn_visualizer/sftp_operations.py +34 -106
- ttnn_visualizer/ssh_client.py +352 -0
- ttnn_visualizer/static/assets/allPaths-esBqnTg5.js +1 -0
- ttnn_visualizer/static/assets/allPathsLoader-KPOKJ-lr.js +2 -0
- ttnn_visualizer/static/assets/index-03c8d4Gh.js +1 -0
- ttnn_visualizer/static/assets/{index-B2fHW2_O.js → index-BANm1CMY.js} +383 -383
- ttnn_visualizer/static/assets/index-BuHal8Ii.css +7 -0
- ttnn_visualizer/static/assets/index-PKNBViIU.js +1 -0
- ttnn_visualizer/static/assets/splitPathsBySizeLoader-DYuDhweD.js +1 -0
- ttnn_visualizer/static/index.html +2 -2
- ttnn_visualizer/views.py +80 -181
- {ttnn_visualizer-0.44.1.dist-info → ttnn_visualizer-0.46.0.dist-info}/METADATA +7 -3
- ttnn_visualizer-0.46.0.dist-info/RECORD +43 -0
- {ttnn_visualizer-0.44.1.dist-info → ttnn_visualizer-0.46.0.dist-info}/licenses/LICENSE +6 -0
- ttnn_visualizer/static/assets/allPaths-CFKU23gh.js +0 -1
- ttnn_visualizer/static/assets/allPathsLoader-CpaihUCo.js +0 -2
- ttnn_visualizer/static/assets/index-B-fsa5Ru.js +0 -1
- ttnn_visualizer/static/assets/index-BueCaPcI.css +0 -7
- ttnn_visualizer/static/assets/index-DLOviMB1.js +0 -1
- ttnn_visualizer/static/assets/splitPathsBySizeLoader-BEb-7YZm.js +0 -1
- ttnn_visualizer-0.44.1.dist-info/RECORD +0 -42
- {ttnn_visualizer-0.44.1.dist-info → ttnn_visualizer-0.46.0.dist-info}/WHEEL +0 -0
- {ttnn_visualizer-0.44.1.dist-info → ttnn_visualizer-0.46.0.dist-info}/entry_points.txt +0 -0
- {ttnn_visualizer-0.44.1.dist-info → ttnn_visualizer-0.46.0.dist-info}/licenses/LICENSE_understanding.txt +0 -0
- {ttnn_visualizer-0.44.1.dist-info → ttnn_visualizer-0.46.0.dist-info}/top_level.txt +0 -0
ttnn_visualizer/csv_queries.py
CHANGED
@@ -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
|
ttnn_visualizer/decorators.py
CHANGED
@@ -90,13 +90,13 @@ def remote_exception_handler(func):
|
|
90
90
|
current_app.logger.error(f"File not found: {str(err)}")
|
91
91
|
raise RemoteConnectionException(
|
92
92
|
status=ConnectionTestStates.FAILED,
|
93
|
-
message=f"Unable to open path
|
93
|
+
message=f"Unable to open path: {str(err)}",
|
94
94
|
)
|
95
95
|
except NoProjectsException as err:
|
96
96
|
current_app.logger.error(f"No projects: {str(err)}")
|
97
97
|
raise RemoteConnectionException(
|
98
98
|
status=ConnectionTestStates.FAILED,
|
99
|
-
message=f"No projects found at remote location: {
|
99
|
+
message=f"No projects found at remote location: {str(err)}",
|
100
100
|
)
|
101
101
|
except NoValidConnectionsError as err:
|
102
102
|
current_app.logger.warning(
|
@@ -124,7 +124,7 @@ def remote_exception_handler(func):
|
|
124
124
|
status=ConnectionTestStates.FAILED, message=message
|
125
125
|
)
|
126
126
|
except IOError as err:
|
127
|
-
message = f"Error opening remote folder
|
127
|
+
message = f"Error opening remote folder: {str(err)}"
|
128
128
|
if "Name or service not known" in str(err):
|
129
129
|
message = f"Unable to connect to {connection.host} - check hostname"
|
130
130
|
raise RemoteConnectionException(
|
ttnn_visualizer/exceptions.py
CHANGED
@@ -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,
|
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
@@ -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
|
-
|
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
|
-
|
81
|
-
|
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=
|
23
|
+
message=str(e),
|
100
24
|
status=ConnectionTestStates.FAILED,
|
101
25
|
)
|
102
26
|
|
@@ -4,7 +4,6 @@
|
|
4
4
|
|
5
5
|
import json
|
6
6
|
import logging
|
7
|
-
import re
|
8
7
|
import subprocess
|
9
8
|
import time
|
10
9
|
from pathlib import Path
|
@@ -16,14 +15,13 @@ from flask import current_app
|
|
16
15
|
from ttnn_visualizer.decorators import remote_exception_handler
|
17
16
|
from ttnn_visualizer.enums import ConnectionTestStates
|
18
17
|
from ttnn_visualizer.exceptions import (
|
19
|
-
AuthenticationException,
|
20
18
|
NoProjectsException,
|
21
|
-
NoValidConnectionsError,
|
22
19
|
RemoteConnectionException,
|
23
20
|
SSHException,
|
24
21
|
)
|
25
22
|
from ttnn_visualizer.models import RemoteConnection, RemoteReportFolder
|
26
23
|
from ttnn_visualizer.sockets import FileProgress, FileStatus, emit_file_status
|
24
|
+
from ttnn_visualizer.ssh_client import SSHClient
|
27
25
|
from ttnn_visualizer.utils import update_last_synced
|
28
26
|
|
29
27
|
logger = logging.getLogger(__name__)
|
@@ -33,53 +31,6 @@ TEST_PROFILER_FILE = "profile_log_device.csv"
|
|
33
31
|
REPORT_DATA_DIRECTORY = Path(__file__).parent.absolute().joinpath("data")
|
34
32
|
|
35
33
|
|
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
34
|
def start_background_task(task, *args):
|
84
35
|
with current_app.app_context():
|
85
36
|
if current_app.config["USE_WEBSOCKETS"]:
|
@@ -663,48 +614,36 @@ def read_remote_file(
|
|
663
614
|
else:
|
664
615
|
path = Path(remote_connection.profilerPath)
|
665
616
|
|
666
|
-
|
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
|
617
|
+
ssh_client = SSHClient(remote_connection)
|
618
|
+
return ssh_client.read_file(path, timeout=30)
|
696
619
|
|
697
620
|
|
698
621
|
@remote_exception_handler
|
699
622
|
def check_remote_path_for_reports(remote_connection):
|
700
|
-
|
701
|
-
remote_config_paths = find_folders_by_files(
|
623
|
+
remote_profiler_paths = find_folders_by_files(
|
702
624
|
remote_connection, remote_connection.profilerPath, [TEST_CONFIG_FILE]
|
703
625
|
)
|
704
|
-
|
626
|
+
|
627
|
+
remote_performance_paths = find_folders_by_files(
|
628
|
+
remote_connection, remote_connection.performancePath, [TEST_PROFILER_FILE]
|
629
|
+
)
|
630
|
+
|
631
|
+
errors = []
|
632
|
+
if not remote_profiler_paths and remote_connection.profilerPath:
|
633
|
+
errors.append(
|
634
|
+
f"No matching profiler projects found: {remote_connection.profilerPath}"
|
635
|
+
)
|
636
|
+
if not remote_performance_paths and remote_connection.performancePath:
|
637
|
+
errors.append(
|
638
|
+
f"No matching performance projects found: {remote_connection.performancePath}"
|
639
|
+
)
|
640
|
+
|
641
|
+
if errors:
|
705
642
|
raise NoProjectsException(
|
706
|
-
message="
|
643
|
+
message="; ".join(errors),
|
644
|
+
status=ConnectionTestStates.FAILED,
|
707
645
|
)
|
646
|
+
|
708
647
|
return True
|
709
648
|
|
710
649
|
|
@@ -713,39 +652,28 @@ def check_remote_path_exists(remote_connection: RemoteConnection, path_key: str)
|
|
713
652
|
"""Check if a remote path exists using SSH test command."""
|
714
653
|
path = getattr(remote_connection, path_key)
|
715
654
|
|
716
|
-
|
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
|
-
)
|
655
|
+
ssh_client = SSHClient(remote_connection)
|
726
656
|
|
727
657
|
try:
|
728
|
-
|
729
|
-
|
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)
|
658
|
+
if ssh_client.check_path_exists(path, timeout=10):
|
659
|
+
return True
|
734
660
|
else:
|
735
661
|
# Directory does not exist or is inaccessible
|
736
662
|
if path_key == "performancePath":
|
737
663
|
message = "Performance directory does not exist or cannot be accessed"
|
738
|
-
|
664
|
+
if path_key == "profilerPath":
|
739
665
|
message = "Profiler directory does not exist or cannot be accessed"
|
666
|
+
else:
|
667
|
+
message = f"Remote path '{path}' does not exist or cannot be accessed"
|
740
668
|
|
741
669
|
logger.error(message)
|
742
670
|
raise RemoteConnectionException(
|
743
671
|
message=message, status=ConnectionTestStates.FAILED
|
744
672
|
)
|
745
|
-
except
|
746
|
-
logger.error(f"
|
673
|
+
except SSHException as e:
|
674
|
+
logger.error(f"Error checking remote path: {path}")
|
747
675
|
raise RemoteConnectionException(
|
748
|
-
message=f"
|
676
|
+
message=f"Error checking remote path: {path}: {str(e)}",
|
749
677
|
status=ConnectionTestStates.FAILED,
|
750
678
|
)
|
751
679
|
|
@@ -902,11 +830,11 @@ def sync_remote_profiler_folders(
|
|
902
830
|
def sync_remote_performance_folders(
|
903
831
|
remote_connection: RemoteConnection,
|
904
832
|
path_prefix: str,
|
905
|
-
|
833
|
+
performance: RemoteReportFolder,
|
906
834
|
exclude_patterns: Optional[List[str]] = None,
|
907
835
|
sid=None,
|
908
836
|
):
|
909
|
-
remote_folder_path =
|
837
|
+
remote_folder_path = performance.remotePath
|
910
838
|
profile_folder = Path(remote_folder_path).name
|
911
839
|
destination_dir = Path(
|
912
840
|
REPORT_DATA_DIRECTORY,
|