ttnn-visualizer 0.69.0__py3-none-any.whl → 0.71.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 (33) hide show
  1. ttnn_visualizer/app.py +32 -1
  2. ttnn_visualizer/csv_queries.py +64 -36
  3. ttnn_visualizer/models.py +0 -1
  4. ttnn_visualizer/queries.py +25 -2
  5. ttnn_visualizer/settings.py +9 -7
  6. ttnn_visualizer/sftp_operations.py +2 -4
  7. ttnn_visualizer/static/assets/allPaths-D6qA1aj4.js +1 -0
  8. ttnn_visualizer/static/assets/allPathsLoader-t8G4bNwo.js +2 -0
  9. ttnn_visualizer/static/assets/{index-C28SjqxA.js → index-8RVye9cY.js} +335 -364
  10. ttnn_visualizer/static/assets/index-BVzPYDVR.css +1 -0
  11. ttnn_visualizer/static/assets/index-BmDjQHI0.js +1 -0
  12. ttnn_visualizer/static/assets/index-CzNKtOwn.js +1 -0
  13. ttnn_visualizer/static/assets/splitPathsBySizeLoader--w3Ey8_r.js +1 -0
  14. ttnn_visualizer/static/index.html +2 -2
  15. ttnn_visualizer/tests/test_queries.py +1 -2
  16. ttnn_visualizer/tests/test_serializers.py +4 -12
  17. ttnn_visualizer/tests/test_utils.py +98 -6
  18. ttnn_visualizer/utils.py +259 -9
  19. ttnn_visualizer/views.py +20 -22
  20. {ttnn_visualizer-0.69.0.dist-info → ttnn_visualizer-0.71.0.dist-info}/METADATA +2 -2
  21. ttnn_visualizer-0.71.0.dist-info/RECORD +44 -0
  22. {ttnn_visualizer-0.69.0.dist-info → ttnn_visualizer-0.71.0.dist-info}/licenses/LICENSE +2 -0
  23. ttnn_visualizer/static/assets/allPaths-Clj2DdFL.js +0 -1
  24. ttnn_visualizer/static/assets/allPathsLoader-DisDEJDi.js +0 -2
  25. ttnn_visualizer/static/assets/index-BZITDwoa.js +0 -1
  26. ttnn_visualizer/static/assets/index-D_AHNWw3.css +0 -1
  27. ttnn_visualizer/static/assets/index-voJy5fZe.js +0 -1
  28. ttnn_visualizer/static/assets/splitPathsBySizeLoader-D98y4BkT.js +0 -1
  29. ttnn_visualizer-0.69.0.dist-info/RECORD +0 -44
  30. {ttnn_visualizer-0.69.0.dist-info → ttnn_visualizer-0.71.0.dist-info}/WHEEL +0 -0
  31. {ttnn_visualizer-0.69.0.dist-info → ttnn_visualizer-0.71.0.dist-info}/entry_points.txt +0 -0
  32. {ttnn_visualizer-0.69.0.dist-info → ttnn_visualizer-0.71.0.dist-info}/licenses/LICENSE_understanding.txt +0 -0
  33. {ttnn_visualizer-0.69.0.dist-info → ttnn_visualizer-0.71.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1 @@
1
+ import{p as r,I as s,_ as a}from"./index-8RVye9cY.js";const n=async(o,_)=>{const i=r(o);let t;return _===s.STANDARD?t=await a(()=>import("./index-BmDjQHI0.js").then(e=>e.I),[]):t=await a(()=>import("./index-CzNKtOwn.js").then(e=>e.I),[]),t[i]};export{n as splitPathsBySizeLoader};
@@ -34,8 +34,8 @@
34
34
  /* SERVER_CONFIG */
35
35
  </script>
36
36
 
37
- <script type="module" crossorigin src="/static/assets/index-C28SjqxA.js"></script>
38
- <link rel="stylesheet" crossorigin href="/static/assets/index-D_AHNWw3.css">
37
+ <script type="module" crossorigin src="/static/assets/index-8RVye9cY.js"></script>
38
+ <link rel="stylesheet" crossorigin href="/static/assets/index-BVzPYDVR.css">
39
39
  </head>
40
40
  <body>
41
41
 
@@ -119,7 +119,6 @@ class TestDatabaseQueries(unittest.TestCase):
119
119
  address_at_first_l1_cb_buffer int,
120
120
  num_banks_per_storage_core int,
121
121
  num_compute_cores int,
122
- num_storage_cores int,
123
122
  total_l1_memory int,
124
123
  total_l1_for_tensors int,
125
124
  total_l1_for_interleaved_buffers int,
@@ -336,7 +335,7 @@ class TestDatabaseQueries(unittest.TestCase):
336
335
 
337
336
  def test_query_devices(self):
338
337
  self.connection.execute(
339
- "INSERT INTO devices VALUES (1, 4, 4, 2, 2, 1024, 4, 256, 0, 0, 1, 2, 2, 4096, 2048, 2048, 2048, 256)"
338
+ "INSERT INTO devices VALUES (1, 4, 4, 2, 2, 1024, 4, 256, 0, 0, 1, 2, 4096, 2048, 2048, 2048, 256)"
340
339
  )
341
340
  results = list(self.db_queries.query_devices(filters={"device_id": 1}))
342
341
  self.assertEqual(len(results), 1)
@@ -65,9 +65,7 @@ class TestSerializers(unittest.TestCase):
65
65
  [25],
66
66
  ),
67
67
  ]
68
- devices = [
69
- Device(1, 4, 4, 2, 2, 256, 4, 64, 0, 0, 1, 2, 512, 256, 128, 64, 1, 512)
70
- ]
68
+ devices = [Device(1, 4, 4, 2, 2, 256, 4, 64, 0, 0, 1, 2, 256, 128, 64, 1, 512)]
71
69
  producers_consumers = [ProducersConsumers(1, [2], [3])]
72
70
  device_operations = [DeviceOperation(1, '[{"counter": 1, "op_id": 1}]')]
73
71
 
@@ -195,10 +193,8 @@ class TestSerializers(unittest.TestCase):
195
193
 
196
194
  def test_serialize_devices(self):
197
195
  devices = [
198
- Device(1, 4, 4, 2, 2, 256, 4, 64, 0, 0, 1, 2, 512, 256, 128, 64, 1, 512),
199
- Device(
200
- 2, 8, 8, 4, 4, 512, 8, 128, 1, 1, 2, 4, 1024, 512, 256, 128, 2, 1024
201
- ),
196
+ Device(1, 4, 4, 2, 2, 256, 4, 64, 0, 0, 1, 2, 256, 128, 64, 1, 512),
197
+ Device(2, 8, 8, 4, 4, 512, 8, 128, 1, 1, 2, 4, 512, 256, 128, 2, 1024),
202
198
  ]
203
199
 
204
200
  result = serialize_devices(devices)
@@ -213,7 +209,6 @@ class TestSerializers(unittest.TestCase):
213
209
  "l1_num_banks": 4,
214
210
  "num_banks_per_storage_core": 1,
215
211
  "num_compute_cores": 2,
216
- "num_storage_cores": 512,
217
212
  "num_x_compute_cores": 2,
218
213
  "num_x_cores": 4,
219
214
  "num_y_compute_cores": 2,
@@ -233,7 +228,6 @@ class TestSerializers(unittest.TestCase):
233
228
  "l1_num_banks": 8,
234
229
  "num_banks_per_storage_core": 2,
235
230
  "num_compute_cores": 4,
236
- "num_storage_cores": 1024,
237
231
  "num_x_compute_cores": 4,
238
232
  "num_x_cores": 8,
239
233
  "num_y_compute_cores": 4,
@@ -373,9 +367,7 @@ class TestSerializers(unittest.TestCase):
373
367
  [200, 300],
374
368
  )
375
369
  ]
376
- devices = [
377
- Device(1, 4, 4, 2, 2, 256, 4, 64, 0, 0, 1, 2, 512, 256, 128, 64, 1, 512)
378
- ]
370
+ devices = [Device(1, 4, 4, 2, 2, 256, 4, 64, 0, 0, 1, 2, 256, 128, 64, 1, 512)]
379
371
  producers_consumers = [ProducersConsumers(1, [2], [3])]
380
372
  device_operations = [DeviceOperation(1, '[{"counter": 1, "op_id": 1}]')]
381
373
 
@@ -2,11 +2,13 @@
2
2
  #
3
3
  # SPDX-FileCopyrightText: © 2025 Tenstorrent AI ULC
4
4
 
5
+ from pathlib import Path
5
6
  from unittest.mock import mock_open, patch
6
7
 
7
8
  from ttnn_visualizer.utils import (
8
9
  find_gunicorn_path,
9
10
  get_app_data_directory,
11
+ get_report_data_directory,
10
12
  is_running_in_container,
11
13
  )
12
14
 
@@ -311,24 +313,34 @@ def test_get_app_data_directory_with_tt_metal_home():
311
313
  assert result == "/path/to/tt-metal/generated/ttnn-visualizer"
312
314
 
313
315
 
314
- def test_get_app_data_directory_with_none():
315
- """Test that get_app_data_directory returns application_dir when tt_metal_home is None."""
316
+ @patch("os.getenv")
317
+ @patch("ttnn_visualizer.utils.is_running_in_container", return_value=False)
318
+ @patch("pathlib.Path.home", return_value=Path("/home/testuser"))
319
+ def test_get_app_data_directory_with_none(mock_home, mock_container, mock_getenv):
320
+ """Test that get_app_data_directory returns ~/.ttnn-visualizer/app when tt_metal_home is None."""
321
+ mock_getenv.return_value = None # No APP_DATA_DIRECTORY env var
316
322
  tt_metal_home = None
317
323
  application_dir = "/default/app/dir"
318
324
 
319
325
  result = get_app_data_directory(tt_metal_home, application_dir)
320
326
 
321
- assert result == "/default/app/dir"
327
+ assert result == "/home/testuser/.ttnn-visualizer/app"
322
328
 
323
329
 
324
- def test_get_app_data_directory_with_empty_string():
325
- """Test that get_app_data_directory treats empty string as falsy and returns application_dir."""
330
+ @patch("os.getenv")
331
+ @patch("ttnn_visualizer.utils.is_running_in_container", return_value=False)
332
+ @patch("pathlib.Path.home", return_value=Path("/home/testuser"))
333
+ def test_get_app_data_directory_with_empty_string(
334
+ mock_home, mock_container, mock_getenv
335
+ ):
336
+ """Test that get_app_data_directory returns ~/.ttnn-visualizer/app when tt_metal_home is empty."""
337
+ mock_getenv.return_value = None # No APP_DATA_DIRECTORY env var
326
338
  tt_metal_home = ""
327
339
  application_dir = "/default/app/dir"
328
340
 
329
341
  result = get_app_data_directory(tt_metal_home, application_dir)
330
342
 
331
- assert result == "/default/app/dir"
343
+ assert result == "/home/testuser/.ttnn-visualizer/app"
332
344
 
333
345
 
334
346
  def test_get_app_data_directory_with_special_characters():
@@ -360,3 +372,83 @@ def test_get_app_data_directory_with_trailing_slash():
360
372
 
361
373
  # Path.join handles trailing slashes correctly
362
374
  assert result == "/path/to/tt-metal/generated/ttnn-visualizer"
375
+
376
+
377
+ @patch("os.getenv")
378
+ @patch("ttnn_visualizer.utils.is_running_in_container", return_value=False)
379
+ @patch("pathlib.Path.home", return_value=Path("/home/testuser"))
380
+ def test_get_app_data_directory_with_env_var(mock_home, mock_container, mock_getenv):
381
+ """Test that get_app_data_directory respects APP_DATA_DIRECTORY environment variable."""
382
+ mock_getenv.side_effect = lambda key, default=None: (
383
+ "/custom/app/data" if key == "APP_DATA_DIRECTORY" else None
384
+ )
385
+ tt_metal_home = None
386
+ application_dir = "/default/app/dir"
387
+
388
+ result = get_app_data_directory(tt_metal_home, application_dir)
389
+
390
+ assert result == "/custom/app/data"
391
+
392
+
393
+ @patch("os.getenv")
394
+ @patch("ttnn_visualizer.utils.is_running_in_container", return_value=True)
395
+ @patch("os.geteuid", return_value=0)
396
+ def test_get_app_data_directory_in_container_as_root(
397
+ mock_geteuid, mock_container, mock_getenv
398
+ ):
399
+ """Test that get_app_data_directory returns /var/lib/ttnn-visualizer/app when running as root in container."""
400
+ mock_getenv.return_value = None # No APP_DATA_DIRECTORY env var
401
+ tt_metal_home = None
402
+ application_dir = "/default/app/dir"
403
+
404
+ result = get_app_data_directory(tt_metal_home, application_dir)
405
+
406
+ assert result == "/var/lib/ttnn-visualizer/app"
407
+
408
+
409
+ @patch("os.getenv")
410
+ @patch("ttnn_visualizer.utils.is_running_in_container", return_value=True)
411
+ @patch("os.geteuid", return_value=1000)
412
+ @patch("pathlib.Path.home", return_value=Path("/home/testuser"))
413
+ def test_get_app_data_directory_in_container_as_non_root(
414
+ mock_home, mock_geteuid, mock_container, mock_getenv
415
+ ):
416
+ """Test that get_app_data_directory returns ~/.ttnn-visualizer/app when running as non-root in container."""
417
+ mock_getenv.return_value = None # No APP_DATA_DIRECTORY env var
418
+ tt_metal_home = None
419
+ application_dir = "/default/app/dir"
420
+
421
+ result = get_app_data_directory(tt_metal_home, application_dir)
422
+
423
+ assert result == "/home/testuser/.ttnn-visualizer/app"
424
+
425
+
426
+ # Tests for get_report_data_directory()
427
+
428
+
429
+ @patch("os.getenv")
430
+ @patch("ttnn_visualizer.utils.is_running_in_container", return_value=False)
431
+ @patch("pathlib.Path.home", return_value=Path("/home/testuser"))
432
+ def test_get_report_data_directory_default(mock_home, mock_container, mock_getenv):
433
+ """Test that get_report_data_directory returns ~/.ttnn-visualizer/reports by default."""
434
+ mock_getenv.return_value = None # No REPORT_DATA_DIRECTORY env var
435
+ tt_metal_home = None
436
+ application_dir = "/default/app/dir"
437
+
438
+ result = get_report_data_directory(tt_metal_home, application_dir)
439
+
440
+ assert result == "/home/testuser/.ttnn-visualizer/reports"
441
+
442
+
443
+ @patch("os.getenv")
444
+ def test_get_report_data_directory_with_env_var(mock_getenv):
445
+ """Test that get_report_data_directory respects REPORT_DATA_DIRECTORY environment variable."""
446
+ mock_getenv.side_effect = lambda key, default=None: (
447
+ "/custom/reports" if key == "REPORT_DATA_DIRECTORY" else None
448
+ )
449
+ tt_metal_home = None
450
+ application_dir = "/default/app/dir"
451
+
452
+ result = get_report_data_directory(tt_metal_home, application_dir)
453
+
454
+ assert result == "/custom/reports"
ttnn_visualizer/utils.py CHANGED
@@ -9,6 +9,7 @@ import logging
9
9
  import os
10
10
  import re
11
11
  import shutil
12
+ import sqlite3
12
13
  import sys
13
14
  import time
14
15
  from functools import wraps
@@ -23,18 +24,271 @@ LAST_SYNCED_FILE_NAME = ".last-synced"
23
24
 
24
25
  def get_app_data_directory(tt_metal_home: Optional[str], application_dir: str) -> str:
25
26
  """
26
- Calculate the APP_DATA_DIRECTORY based on TT_METAL_HOME or fallback to application_dir.
27
+ Calculate the APP_DATA_DIRECTORY with sensible defaults.
28
+
29
+ Priority:
30
+ 1. TT_METAL_HOME (if set) -> {tt_metal_home}/generated/ttnn-visualizer
31
+ 2. Environment variable APP_DATA_DIRECTORY (if set)
32
+ 3. Container detection -> /var/lib/ttnn-visualizer/app (root) or ~/.ttnn-visualizer/app (non-root)
33
+ 4. Regular user -> ~/.ttnn-visualizer/app
27
34
 
28
35
  Args:
29
36
  tt_metal_home: Path to TT-Metal home directory, or None
30
- application_dir: Fallback application directory path
37
+ application_dir: Fallback application directory path (legacy, used for migration detection)
31
38
 
32
39
  Returns:
33
40
  Path to the app data directory
34
41
  """
42
+ # Priority 1: TT_METAL_HOME mode
35
43
  if tt_metal_home and tt_metal_home.strip():
36
44
  return str(Path(tt_metal_home).expanduser() / "generated" / "ttnn-visualizer")
37
- return application_dir
45
+
46
+ # Priority 2: Explicit environment variable
47
+ if env_dir := os.getenv("APP_DATA_DIRECTORY"):
48
+ return env_dir
49
+
50
+ # Priority 3: Container detection
51
+ if is_running_in_container():
52
+ # If running as root in container, use /var/lib
53
+ try:
54
+ if os.geteuid() == 0:
55
+ return "/var/lib/ttnn-visualizer/app"
56
+ except AttributeError:
57
+ # Windows doesn't have geteuid(), assume non-root
58
+ pass
59
+ # Otherwise use home directory (even in container)
60
+ return str(Path.home() / ".ttnn-visualizer" / "app")
61
+
62
+ # Priority 4: Default for regular users
63
+ return str(Path.home() / ".ttnn-visualizer" / "app")
64
+
65
+
66
+ def get_report_data_directory(
67
+ tt_metal_home: Optional[str], application_dir: str
68
+ ) -> str:
69
+ """
70
+ Calculate the REPORT_DATA_DIRECTORY with sensible defaults.
71
+
72
+ Uses the same base directory as app data, but points to reports subdirectory.
73
+ Structure: {base}/reports (where base is ~/.ttnn-visualizer or /var/lib/ttnn-visualizer)
74
+
75
+ Args:
76
+ tt_metal_home: Path to TT-Metal home directory, or None
77
+ application_dir: Fallback application directory path (legacy, used for migration detection)
78
+
79
+ Returns:
80
+ Path to the report data directory
81
+ """
82
+ # Priority 1: Explicit environment variable
83
+ if env_dir := os.getenv("REPORT_DATA_DIRECTORY"):
84
+ return env_dir
85
+
86
+ # Priority 2: Use same base as app data, but in reports subdirectory
87
+ app_data_dir = get_app_data_directory(tt_metal_home, application_dir)
88
+ base_dir = Path(app_data_dir).parent
89
+ return str(base_dir / "reports")
90
+
91
+
92
+ def migrate_old_data_directory(
93
+ old_app_data_dir: str,
94
+ old_report_data_dir: str,
95
+ new_app_data_dir: str,
96
+ new_report_data_dir: str,
97
+ db_version: str,
98
+ ) -> bool:
99
+ """
100
+ Migrate data from old site-packages directory to new user directory.
101
+
102
+ Args:
103
+ old_app_data_dir: Old app data directory (typically in site-packages)
104
+ old_report_data_dir: Old report data directory (typically in site-packages)
105
+ new_app_data_dir: New app data directory (typically ~/.ttnn-visualizer/app)
106
+ new_report_data_dir: New report data directory (typically ~/.ttnn-visualizer/reports)
107
+ db_version: Database version string (e.g., "0.29.0") to construct database filename
108
+
109
+ Returns:
110
+ True if migration was performed, False otherwise
111
+ """
112
+ old_app_path = Path(old_app_data_dir)
113
+ old_report_path = Path(old_report_data_dir)
114
+ new_app_path = Path(new_app_data_dir)
115
+ new_report_path = Path(new_report_data_dir)
116
+
117
+ # Construct the database filename
118
+ db_filename = f"ttnn_{db_version}.db"
119
+ old_db_path = old_app_path / db_filename
120
+
121
+ # Check if old directories exist and have data
122
+ old_app_has_data = old_db_path.exists()
123
+ old_report_has_data = old_report_path.exists() and any(old_report_path.iterdir())
124
+
125
+ if not old_app_has_data and not old_report_has_data:
126
+ return False
127
+
128
+ # Check if new directories already have data (don't overwrite)
129
+ new_db_path = new_app_path / db_filename
130
+ new_app_has_data = new_db_path.exists()
131
+ new_report_has_data = new_report_path.exists() and any(new_report_path.iterdir())
132
+
133
+ if new_app_has_data or new_report_has_data:
134
+ logger.info(
135
+ f"New data directories already exist with data, skipping migration. "
136
+ f"App: {new_app_path}, Reports: {new_report_path}"
137
+ )
138
+ return False
139
+
140
+ # Check if old directory is actually in site-packages (to avoid migrating from custom locations)
141
+ old_app_str = str(old_app_path)
142
+ if "site-packages" not in old_app_str and "dist-packages" not in old_app_str:
143
+ logger.info(
144
+ f"Old app data directory is not in site-packages, skipping migration: {old_app_path}"
145
+ )
146
+ return False
147
+
148
+ print("\n" + "=" * 70)
149
+ print("📦 DATA DIRECTORY MIGRATION")
150
+ print("=" * 70)
151
+ print(f"Detected old data in site-packages directory.")
152
+ print(f" Old app data: {old_app_path}")
153
+ print(f" Old reports: {old_report_path}")
154
+ print(f"\nNew location:")
155
+ print(f" New app data: {new_app_path}")
156
+ print(f" New reports: {new_report_path}")
157
+ print("\nWould you like to migrate the data? (y/n): ", end="", flush=True)
158
+
159
+ try:
160
+ response = input().strip().lower()
161
+ if response not in ("y", "yes"):
162
+ print("Migration cancelled by user.")
163
+ return False
164
+ except (EOFError, KeyboardInterrupt):
165
+ print("\nMigration cancelled.")
166
+ return False
167
+
168
+ # Create new directories
169
+ new_app_path.mkdir(parents=True, exist_ok=True)
170
+ new_report_path.mkdir(parents=True, exist_ok=True)
171
+
172
+ migrated = False
173
+
174
+ # Migrate app data (only the specific database file)
175
+ if old_app_has_data:
176
+ print(f"\nMigrating database file from {old_app_path} to {new_app_path}...")
177
+ try:
178
+ # Move the database file
179
+ shutil.move(str(old_db_path), str(new_db_path))
180
+ print(f" ✓ Moved {db_filename}")
181
+ migrated = True
182
+ except Exception as e:
183
+ logger.error(f"Error migrating database file: {e}")
184
+ print(f" ❌ Error: {e}")
185
+
186
+ # Migrate report data (all files and directories)
187
+ if old_report_has_data:
188
+ print(f"\nMigrating reports from {old_report_path} to {new_report_path}...")
189
+ try:
190
+ for item in old_report_path.iterdir():
191
+ dest = new_report_path / item.name
192
+ if item.is_file():
193
+ shutil.move(str(item), str(dest))
194
+ print(f" ✓ Moved {item.name}")
195
+ elif item.is_dir():
196
+ shutil.move(str(item), str(dest))
197
+ print(f" ✓ Moved directory {item.name}")
198
+ migrated = True
199
+ except Exception as e:
200
+ logger.error(f"Error migrating report data: {e}")
201
+ print(f" ❌ Error: {e}")
202
+
203
+ # Update paths in the database after migration
204
+ # Note: We use the old_report_path for matching even though files are moved,
205
+ # because the database still contains the old paths that need to be updated
206
+ if migrated and old_app_has_data:
207
+ print(f"\nUpdating paths in database...")
208
+ try:
209
+ _update_database_paths(new_db_path, old_report_path, new_report_path)
210
+ print(f" ✓ Updated paths in database")
211
+ except Exception as e:
212
+ logger.error(f"Error updating database paths: {e}")
213
+ print(f" ⚠️ Warning: Could not update paths in database: {e}")
214
+ print(f" You may need to manually update paths in the instances table.")
215
+
216
+ if migrated:
217
+ print("\n✅ Migration completed successfully!")
218
+ print(f" Data has been moved from: {old_app_path}")
219
+ else:
220
+ print("\n⚠️ No data was migrated.")
221
+
222
+ print("=" * 70 + "\n")
223
+
224
+ return migrated
225
+
226
+
227
+ def _update_database_paths(
228
+ db_path: Path, old_report_data_dir: Path, new_report_data_dir: Path
229
+ ) -> None:
230
+ """
231
+ Update absolute paths in the instances table after migration.
232
+
233
+ Args:
234
+ db_path: Path to the SQLite database file
235
+ old_report_data_dir: Old report data directory path
236
+ new_report_data_dir: New report data directory path
237
+ """
238
+ # Normalize paths to handle symlinks and ensure consistent format
239
+ old_report_data_dir = old_report_data_dir.resolve()
240
+ new_report_data_dir = new_report_data_dir.resolve()
241
+ old_report_str = str(old_report_data_dir)
242
+ new_report_str = str(new_report_data_dir)
243
+
244
+ try:
245
+ conn = sqlite3.connect(str(db_path))
246
+ cursor = conn.cursor()
247
+
248
+ # Update profiler_path
249
+ cursor.execute(
250
+ """
251
+ UPDATE instances
252
+ SET profiler_path = REPLACE(profiler_path, ?, ?)
253
+ WHERE profiler_path LIKE ? || '%'
254
+ """,
255
+ (old_report_str, new_report_str, old_report_str),
256
+ )
257
+ profiler_updated = cursor.rowcount
258
+
259
+ # Update performance_path
260
+ cursor.execute(
261
+ """
262
+ UPDATE instances
263
+ SET performance_path = REPLACE(performance_path, ?, ?)
264
+ WHERE performance_path LIKE ? || '%'
265
+ """,
266
+ (old_report_str, new_report_str, old_report_str),
267
+ )
268
+ performance_updated = cursor.rowcount
269
+
270
+ # Update npe_path
271
+ cursor.execute(
272
+ """
273
+ UPDATE instances
274
+ SET npe_path = REPLACE(npe_path, ?, ?)
275
+ WHERE npe_path LIKE ? || '%'
276
+ """,
277
+ (old_report_str, new_report_str, old_report_str),
278
+ )
279
+ npe_updated = cursor.rowcount
280
+
281
+ conn.commit()
282
+ conn.close()
283
+
284
+ if profiler_updated > 0 or performance_updated > 0 or npe_updated > 0:
285
+ logger.info(
286
+ f"Updated database paths: {profiler_updated} profiler_path, "
287
+ f"{performance_updated} performance_path, {npe_updated} npe_path"
288
+ )
289
+ except sqlite3.Error as e:
290
+ logger.error(f"SQLite error updating paths: {e}")
291
+ raise
38
292
 
39
293
 
40
294
  def find_gunicorn_path() -> tuple[str, Optional[str]]:
@@ -428,13 +682,9 @@ def get_mesh_descriptor_paths(instance):
428
682
  return []
429
683
 
430
684
  parent = Path(instance.profiler_path).parent
431
- pattern = re.compile(r"physical_chip_mesh_coordinate_mapping_[1-8]_of_[1-8]\.yaml")
685
+ glob_pattern = "physical_chip_mesh_coordinate_mapping_[0-9]_of_[0-9].yaml"
432
686
 
433
- return sorted(
434
- str(path)
435
- for path in parent.iterdir()
436
- if path.is_file() and pattern.fullmatch(path.name)
437
- )
687
+ return sorted(str(path) for path in parent.glob(glob_pattern) if path.is_file())
438
688
 
439
689
 
440
690
  def read_last_synced_file(directory: str) -> Optional[int]:
ttnn_visualizer/views.py CHANGED
@@ -732,9 +732,10 @@ def get_performance_results_report(instance: Instance):
732
732
  start_signpost = request.args.get("start_signpost", None)
733
733
  end_signpost = request.args.get("end_signpost", None)
734
734
  print_signposts = str_to_bool(request.args.get("print_signposts", "true"))
735
- stack_by_in0 = str_to_bool(request.args.get("stack_by_in0", "true"))
736
735
  hide_host_ops = str_to_bool(request.args.get("hide_host_ops", "true"))
737
736
  merge_devices = str_to_bool(request.args.get("merge_devices", "true"))
737
+ tracing_mode = str_to_bool(request.args.get("tracing_mode", "false"))
738
+ group_by = request.args.get("group_by", None)
738
739
 
739
740
  if name and not current_app.config["SERVER_MODE"]:
740
741
  performance_path = Path(instance.performance_path).parent / name
@@ -744,12 +745,13 @@ def get_performance_results_report(instance: Instance):
744
745
  try:
745
746
  report = OpsPerformanceReportQueries.generate_report(
746
747
  instance,
747
- stack_by_in0=stack_by_in0,
748
748
  start_signpost=start_signpost,
749
749
  print_signposts=print_signposts,
750
750
  end_signpost=end_signpost,
751
751
  hide_host_ops=hide_host_ops,
752
752
  merge_devices=merge_devices,
753
+ tracing_mode=tracing_mode,
754
+ group_by=group_by,
753
755
  )
754
756
  except DataFormatError:
755
757
  return Response(status=HTTPStatus.UNPROCESSABLE_ENTITY)
@@ -1164,29 +1166,25 @@ def get_cluster_descriptor(instance: Instance):
1164
1166
  @api.route("/mesh-descriptor", methods=["GET"])
1165
1167
  @with_instance
1166
1168
  def get_mesh_descriptor(instance: Instance):
1167
- if instance.remote_connection:
1169
+ paths = get_mesh_descriptor_paths(instance)
1170
+
1171
+ if not paths:
1168
1172
  return (
1169
- jsonify({"error": "Remote mesh descriptor is not yet supported"}),
1170
- HTTPStatus.NOT_IMPLEMENTED,
1173
+ jsonify(
1174
+ {"error": "physical_chip_mesh_coordinate_mapping_1_of_1.yaml not found"}
1175
+ ),
1176
+ HTTPStatus.NOT_FOUND,
1171
1177
  )
1172
- else:
1173
- paths = get_mesh_descriptor_paths(instance)
1174
- if not paths:
1175
- return jsonify({"error": "mesh.yaml not found"}), 404
1176
1178
 
1177
- local_path = paths[0]
1178
-
1179
- if not local_path:
1180
- return jsonify({"error": "mesh.yaml not found"}), 404
1181
-
1182
- try:
1183
- with open(local_path) as mesh_descriptor_path:
1184
- yaml_data = yaml.safe_load(mesh_descriptor_path)
1185
- return jsonify(yaml_data) # yaml_data is not compatible with orjson
1186
- except yaml.YAMLError as e:
1187
- return jsonify({"error": f"Failed to parse YAML: {str(e)}"}), 400
1188
-
1189
- return jsonify({"error": "Mesh descriptor not found"}), 404
1179
+ try:
1180
+ with open(paths[0]) as mesh_descriptor_path:
1181
+ yaml_data = yaml.safe_load(mesh_descriptor_path)
1182
+ return jsonify(yaml_data) # yaml_data is not compatible with orjson
1183
+ except yaml.YAMLError as e:
1184
+ return (
1185
+ jsonify({"error": f"Failed to parse YAML: {str(e)}"}),
1186
+ HTTPStatus.BAD_REQUEST,
1187
+ )
1190
1188
 
1191
1189
 
1192
1190
  @api.route("/remote/test", methods=["POST"])
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ttnn_visualizer
3
- Version: 0.69.0
3
+ Version: 0.71.0
4
4
  Summary: TT-NN Visualizer
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -23,7 +23,7 @@ Requires-Dist: pydantic_core==2.27.1
23
23
  Requires-Dist: pydantic==2.10.3
24
24
  Requires-Dist: python-dotenv==1.0.1
25
25
  Requires-Dist: PyYAML==6.0.2
26
- Requires-Dist: tt-perf-report==1.1.14
26
+ Requires-Dist: tt-perf-report==1.2.1
27
27
  Requires-Dist: uvicorn==0.30.1
28
28
  Requires-Dist: zstd==1.5.7.0
29
29
  Provides-Extra: dev
@@ -0,0 +1,44 @@
1
+ ttnn_visualizer/__init__.py,sha256=FCQeTWnXsf-Wx-fay53-lQsm0y5-GcPMUmzhE5upDx0,93
2
+ ttnn_visualizer/app.py,sha256=fiOg_aUMLPGZu_FjUemgpwJYzz6uE50lEvOcy8cuBp4,14631
3
+ ttnn_visualizer/csv_queries.py,sha256=Q3lLhcZ8FWelTEPHg7j9qaakeeFqltHLGEW8nOVwDpw,21897
4
+ ttnn_visualizer/decorators.py,sha256=YY_5zQdD9VOuBC5iP1Z9CEMjYz3ofD1Lc3ZrBvzQx8U,5779
5
+ ttnn_visualizer/enums.py,sha256=SEIqp1tlc_zw2vQ8nHH9YTaV0m3Cb8fjn_goqz5wurE,203
6
+ ttnn_visualizer/exceptions.py,sha256=KVZzb7YaWbq51DNMKPBcJHwG74RMYj_29WTSYOlXXeU,2096
7
+ ttnn_visualizer/extensions.py,sha256=6OIRJ8-_ccfjOaXSruRXiS29jEbxp4Pyk-0JlD8IHBQ,379
8
+ ttnn_visualizer/file_uploads.py,sha256=HFcC6TBt5I0oBkiKgM2Qw1W7hpixE8TOTACS5N-rmGE,5013
9
+ ttnn_visualizer/instances.py,sha256=yoWGV0I3xNm8nFZrQHJMq7k6C_moFiic9ZgMkgfxsHU,13406
10
+ ttnn_visualizer/models.py,sha256=6z0WwKWwaOhYMgHEIM5NHxiybDoipL3vJD1uRgZPuSI,8620
11
+ ttnn_visualizer/pytest_plugin.py,sha256=bEG0cbqH0HUuZT5Ox9tFoexFNTyimBBPpI_jp75b54c,2629
12
+ ttnn_visualizer/queries.py,sha256=gfI8Jzlp7VHCh8VDSqgYqbhlf9eaj2QvwkXovNCjqXg,13620
13
+ ttnn_visualizer/serializers.py,sha256=mKxcDu9g4gAxHB6wP_1l5VJvIBmnYDIJTikiaMYXupg,9374
14
+ ttnn_visualizer/settings.py,sha256=4R9LVsxs9XRtJx6f7oW84jPeq4ffuDZkN2QEVE8Ie8Q,5216
15
+ ttnn_visualizer/sftp_operations.py,sha256=l5MEorNJ93e9X_Nf7fgLb3cSftuErE3PKzAki2T5Hpc,31585
16
+ ttnn_visualizer/sockets.py,sha256=_Hdne33r4FrB2tg58Vw87FWLbgQ_ikICVp4o1Mkv2mo,4789
17
+ ttnn_visualizer/ssh_client.py,sha256=x-BUUnsaKGReuOrSpHdcIaoH6RdGiQQYWx2_pOkGzJ0,13410
18
+ ttnn_visualizer/utils.py,sha256=CzFX4AuVmHaAxq6ZklUk3PMWPliNb4-seAtRuXiDthg,27995
19
+ ttnn_visualizer/views.py,sha256=oD31TaN-g5Fb7G7oDFyRtuJYBUON_HGjGxI-EnQtF4Q,52477
20
+ ttnn_visualizer/static/index.html,sha256=QxpENVT5XwOnFv_Jh-AgcO3rBZRrI4rwG7zz3u738FM,1135
21
+ ttnn_visualizer/static/assets/allPaths-D6qA1aj4.js,sha256=BnjOCm8ifqgwLBAmRP5cO-lpMnZfAySNyoZoooU7vIA,255
22
+ ttnn_visualizer/static/assets/allPathsLoader-t8G4bNwo.js,sha256=j-q8s8ju02Aqy_3WaBo3fNtPHRw6tadfqH4aAdP-U7k,477
23
+ ttnn_visualizer/static/assets/index-8RVye9cY.js,sha256=-u_M4wiPslSSnTuq2OMyf2X_czVdzr1wUnTScyE6P_E,7982508
24
+ ttnn_visualizer/static/assets/index-BVzPYDVR.css,sha256=_fSlNYmeC54u6mXYoyiQDIXeRD9ajwaKIWE2M7e1uJk,618970
25
+ ttnn_visualizer/static/assets/index-BmDjQHI0.js,sha256=Np8PB0IkYR44ybu3HDpLI7b6XWw2sZQL-MIbQxV7CcA,294299
26
+ ttnn_visualizer/static/assets/index-CzNKtOwn.js,sha256=i4B-DZkbAK68FWdRZAGppuLcytv1ETOLu6m6s_0a7r4,303575
27
+ ttnn_visualizer/static/assets/site-BTBrvHC5.webmanifest,sha256=Uy_XmnGuYFVf-OZuma2NvgEPdrCrevb3HZvaxSIHoA0,456
28
+ ttnn_visualizer/static/assets/splitPathsBySizeLoader--w3Ey8_r.js,sha256=KAjd3n5fEUXoGPOA7V5JrfsPWnatp8tyl6xZ9LgqpBk,281
29
+ ttnn_visualizer/static/favicon/android-chrome-192x192.png,sha256=BZWA09Zxaa3fXbaeS6nhWo2e-DUSjm9ElzNQ_xTB5XU,6220
30
+ ttnn_visualizer/static/favicon/android-chrome-512x512.png,sha256=HBiJSZyguB3o8fMJuqIGcpeBy_9JOdImme3wD02UYCw,62626
31
+ ttnn_visualizer/static/favicon/favicon-32x32.png,sha256=Zw201qUsczQv1UvoQvJf5smQ2ss10xaTeWxmQNYCGtY,480
32
+ ttnn_visualizer/static/favicon/favicon.svg,sha256=wDPY3VrekJ_DE1TnJ2vUy602K3S4Xe9TgrdZ7jXK9c8,633
33
+ ttnn_visualizer/static/sample-data/cluster-desc.yaml,sha256=LMxOmsRUXtVVU5ogzYkXUozB3dg2IzqIRJQpV_O5qMU,29618
34
+ ttnn_visualizer/tests/__init__.py,sha256=FCQeTWnXsf-Wx-fay53-lQsm0y5-GcPMUmzhE5upDx0,93
35
+ ttnn_visualizer/tests/test_queries.py,sha256=3je_FKRgZLdoDTfIe6kENLFXWUeeYVgY3ZDVnvWgCJo,13754
36
+ ttnn_visualizer/tests/test_serializers.py,sha256=KLzkqQhDlqPTaJ5MUy-r7Nb2WT2hp5aTm_ceA0tlxIo,18911
37
+ ttnn_visualizer/tests/test_utils.py,sha256=bI2oMdir9BIy1RsIXTn80VTOrnleL5exOMjpDfhp6JY,14938
38
+ ttnn_visualizer-0.71.0.dist-info/licenses/LICENSE,sha256=DsbsWC-ymVM85l4HKV_ruheRbr37-FJ8KGxL0_cZdb0,20463
39
+ ttnn_visualizer-0.71.0.dist-info/licenses/LICENSE_understanding.txt,sha256=pymi-yb_RvYM9p2ZA4iSNsImcvhDBBxlGuJCY9dTq7M,233
40
+ ttnn_visualizer-0.71.0.dist-info/METADATA,sha256=iScWAvGleF5cr1BuoDS-sWLI-GoQK2joTw-dVJ8QE8A,8911
41
+ ttnn_visualizer-0.71.0.dist-info/WHEEL,sha256=lTU6B6eIfYoiQJTZNc-fyaR6BpL6ehTzU3xGYxn2n8k,91
42
+ ttnn_visualizer-0.71.0.dist-info/entry_points.txt,sha256=QpuUpkmQ_mEHJTMqOBdU0MH2Z4WF_9iFsGACeyyAO1E,61
43
+ ttnn_visualizer-0.71.0.dist-info/top_level.txt,sha256=M1EGkvDOuIfbhDbcUdz2-TSdmCtDoQ2Uyag9k5JLDSY,16
44
+ ttnn_visualizer-0.71.0.dist-info/RECORD,,
@@ -87,6 +87,8 @@ The following separate and independent dependencies are utilized by this project
87
87
  - @eslint/eslintrc - MIT - https://github.com/eslint/eslintrc/blob/main/LICENSE
88
88
  - @eslint/js - MIT - https://github.com/eslint/eslint/blob/main/LICENSE
89
89
  - @eslint/rewrite - Apache-2.0 - https://github.com/eslint/rewrite/blob/main/LICENSE
90
+ - @tanstack/react-query - MIT - https://github.com/TanStack/query/blob/main/LICENSE
91
+ - @tanstack/react-query-devtools - MIT - https://github.com/TanStack/query/blob/main/LICENSE
90
92
  - @tanstack/react-virtual - MIT - https://github.com/TanStack/virtual/blob/main/LICENSE
91
93
  - @testing-library/dom - MIT - https://github.com/testing-library/dom-testing-library/blob/main/LICENSE
92
94
  - @testing-library/jest-dom - MIT - https://github.com/testing-library/jest-dom/blob/main/LICENSE
@@ -1 +0,0 @@
1
- import{I as s}from"./index-voJy5fZe.js";import{I as r}from"./index-BZITDwoa.js";import{p as n,I as c}from"./index-C28SjqxA.js";function p(t,a){const o=n(t);return a===c.STANDARD?s[o]:r[o]}export{s as IconSvgPaths16,r as IconSvgPaths20,p as getIconPaths};