ttnn-visualizer 0.62.0__py3-none-any.whl → 0.63.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/app.py CHANGED
@@ -25,7 +25,7 @@ from ttnn_visualizer.exceptions import (
25
25
  )
26
26
  from ttnn_visualizer.instances import create_instance_from_local_paths
27
27
  from ttnn_visualizer.settings import Config, DefaultConfig
28
- from ttnn_visualizer.utils import create_path_resolver
28
+ from ttnn_visualizer.utils import find_gunicorn_path
29
29
  from werkzeug.debug import DebuggedApplication
30
30
  from werkzeug.middleware.proxy_fix import ProxyFix
31
31
 
@@ -115,7 +115,6 @@ def extensions(app: flask.Flask):
115
115
  :param app: Flask application instance
116
116
  :return: None
117
117
  """
118
-
119
118
  flask_static_digest.init_app(app)
120
119
  if app.config["USE_WEBSOCKETS"]:
121
120
  socketio.init_app(app)
@@ -124,14 +123,9 @@ def extensions(app: flask.Flask):
124
123
  if app.config["USE_WEBSOCKETS"]:
125
124
  register_handlers(socketio)
126
125
 
127
- # Create the tables within the application context
128
126
  with app.app_context():
129
127
  db.create_all()
130
128
 
131
- # For automatically reflecting table data
132
- # with app.app_context():
133
- # db.reflect()
134
-
135
129
  return None
136
130
 
137
131
 
@@ -198,63 +192,91 @@ def parse_args():
198
192
  parser.add_argument(
199
193
  "--tt-metal-home", help="Specify a TT-Metal home path", default=None
200
194
  )
195
+ parser.add_argument(
196
+ "-d",
197
+ "--daemon",
198
+ action="store_true",
199
+ help="Run the server as a daemon process",
200
+ )
201
201
  return parser.parse_args()
202
202
 
203
203
 
204
+ def display_mode_info_without_db(config):
205
+ """Display mode information using only config, without initializing database."""
206
+ # Determine if we're in TT-Metal mode
207
+ tt_metal_home = config.TT_METAL_HOME
208
+ is_tt_metal_mode = tt_metal_home is not None
209
+
210
+ if is_tt_metal_mode:
211
+ print("🚀 TT-METAL MODE: Working directly with tt-metal generated directory")
212
+ print(f" TT_METAL_HOME: {tt_metal_home}")
213
+
214
+ profiler_base = Path(tt_metal_home) / "generated" / "ttnn" / "reports"
215
+ performance_base = Path(tt_metal_home) / "generated" / "profiler" / "reports"
216
+
217
+ print(f" Profiler reports: {profiler_base}")
218
+ print(f" Performance reports: {performance_base}")
219
+
220
+ # Validate setup
221
+ if not Path(tt_metal_home).exists():
222
+ print(
223
+ f" ⚠️ Warning: TT_METAL_HOME directory does not exist: {tt_metal_home}"
224
+ )
225
+ elif not (Path(tt_metal_home) / "generated").exists():
226
+ print(f" ⚠️ Warning: TT-Metal generated directory not found")
227
+ elif not profiler_base.exists():
228
+ print(
229
+ f" ⚠️ Warning: Profiler reports directory not found: {profiler_base}"
230
+ )
231
+ elif not performance_base.exists():
232
+ print(
233
+ f" ⚠️ Warning: Performance reports directory not found: {performance_base}"
234
+ )
235
+ else:
236
+ print(f" ✓ TT-Metal setup is valid")
237
+ else:
238
+ print(
239
+ "📁 UPLOAD/SYNC MODE: Using local data directory for uploaded/synced reports"
240
+ )
241
+ print(f" Local directory: {config.LOCAL_DATA_DIRECTORY}")
242
+ print(f" Remote directory: {config.REMOTE_DATA_DIRECTORY}")
243
+
244
+
204
245
  def main():
205
246
 
206
247
  run_command = sys.argv[0].split("/")
207
248
  if run_command[-1] == "ttnn-visualizer":
208
249
  os.environ.setdefault("FLASK_ENV", "production")
209
250
 
210
- config = cast(DefaultConfig, Config())
211
251
  args = parse_args()
212
- instance_id = None
213
252
 
214
- if args.profiler_path or args.performance_path:
215
- app = create_app()
216
- app.app_context().push()
217
- try:
218
- session = create_instance_from_local_paths(
219
- profiler_path=args.profiler_path,
220
- performance_path=args.performance_path,
221
- )
222
- except InvalidReportPath:
223
- sys.exit("Invalid report path")
224
- except InvalidProfilerPath:
225
- sys.exit("Invalid profiler path")
226
-
227
- instance_id = session.instance_id
253
+ config = cast(DefaultConfig, Config())
254
+ instance_id = None
228
255
 
256
+ # Display mode information first (using config only, no DB needed)
229
257
  if args.tt_metal_home:
230
258
  config.TT_METAL_HOME = args.tt_metal_home
231
259
 
232
- # Display mode information
233
- app = create_app()
234
- with app.app_context():
235
- resolver = create_path_resolver(app)
236
- mode_info = resolver.get_mode_info()
260
+ display_mode_info_without_db(config)
237
261
 
238
- if mode_info["mode"] == "tt_metal":
239
- print(
240
- "🚀 TT-METAL MODE: Working directly with tt-metal generated directory"
241
- )
242
- print(f" TT_METAL_HOME: {mode_info['tt_metal_home']}")
243
- print(f" Profiler reports: {mode_info['profiler_base']}")
244
- print(f" Performance reports: {mode_info['performance_base']}")
245
-
246
- # Validate setup
247
- is_valid, message = resolver.validate_tt_metal_setup()
248
- if is_valid:
249
- print(f" ✓ {message}")
250
- else:
251
- print(f" ⚠️ Warning: {message}")
252
- else:
253
- print(
254
- "📁 UPLOAD/SYNC MODE: Using local data directory for uploaded/synced reports"
255
- )
256
- print(f" Local directory: {mode_info['local_dir']}")
257
- print(f" Remote directory: {mode_info['remote_dir']}")
262
+ # If profiler/performance paths are provided, create an instance
263
+ # This requires DB access, so we create the app temporarily
264
+ if args.profiler_path or args.performance_path:
265
+ app = create_app()
266
+ with app.app_context():
267
+ try:
268
+ session = create_instance_from_local_paths(
269
+ profiler_path=args.profiler_path,
270
+ performance_path=args.performance_path,
271
+ )
272
+ instance_id = session.instance_id
273
+ except InvalidReportPath:
274
+ sys.exit("Invalid report path")
275
+ except InvalidProfilerPath:
276
+ sys.exit("Invalid profiler path")
277
+
278
+ # Clean up this temporary app - workers will create their own
279
+ del app
258
280
 
259
281
  # Check if DEBUG environment variable is set
260
282
  debug_mode = os.environ.get("DEBUG", "false").lower() == "true"
@@ -263,8 +285,19 @@ def main():
263
285
  for key, value in config.to_dict().items():
264
286
  print(f"{key}={value}")
265
287
 
288
+ # Warn if there's a gunicorn config file in current directory
289
+ if Path("gunicorn.conf.py").exists():
290
+ logger.warning(
291
+ "Found gunicorn.conf.py in current directory - this may override environment settings"
292
+ )
293
+
294
+ gunicorn_cmd, gunicorn_warning = find_gunicorn_path()
295
+
296
+ if gunicorn_warning:
297
+ print(gunicorn_warning)
298
+
266
299
  gunicorn_args = [
267
- "gunicorn",
300
+ gunicorn_cmd,
268
301
  "-t",
269
302
  config.GUNICORN_TIMEOUT,
270
303
  "-k",
@@ -279,7 +312,10 @@ def main():
279
312
  if debug_mode:
280
313
  gunicorn_args.insert(1, "--reload")
281
314
 
282
- if config.LAUNCH_BROWSER_ON_START:
315
+ if args.daemon:
316
+ gunicorn_args.insert(1, "--daemon")
317
+
318
+ if config.LAUNCH_BROWSER_ON_START and not args.daemon:
283
319
  flask_env = os.getenv("FLASK_ENV", "development")
284
320
  port = config.PORT if flask_env == "production" else config.DEV_SERVER_PORT
285
321
  host = config.HOST if flask_env == "production" else config.DEV_SERVER_HOST
@@ -463,7 +463,7 @@ class OpsPerformanceReportQueries:
463
463
 
464
464
  try:
465
465
  perf_report.generate_perf_report(
466
- csv_file,
466
+ [csv_file],
467
467
  signpost,
468
468
  ignore_signposts,
469
469
  cls.DEFAULT_MIN_PERCENTAGE,
@@ -497,7 +497,12 @@ class OpsPerformanceReportQueries:
497
497
  op_id = index + 2 # Match IDs with row numbers in ops perf results csv
498
498
  if not any(s["op_code"] == op_code for s in signposts):
499
499
  captured_signposts.add(op_code)
500
- signposts.append({"id": op_id, "op_code": op_code})
500
+ signposts.append(
501
+ {
502
+ "id": op_id,
503
+ "op_code": op_code,
504
+ }
505
+ )
501
506
 
502
507
  report = []
503
508
 
@@ -6,6 +6,7 @@ import os
6
6
  from pathlib import Path
7
7
 
8
8
  from dotenv import load_dotenv
9
+ from sqlalchemy.pool import NullPool
9
10
  from ttnn_visualizer.utils import str_to_bool
10
11
 
11
12
  load_dotenv()
@@ -55,13 +56,17 @@ class DefaultConfig(object):
55
56
  USE_WEBSOCKETS = str_to_bool(os.getenv("USE_WEBSOCKETS", "true"))
56
57
 
57
58
  # SQL Alchemy Settings
58
- SQLALCHEMY_DATABASE_URI = (
59
- f"sqlite:///{os.path.join(APP_DATA_DIRECTORY, f'ttnn_{DB_VERSION}.db')}"
60
- )
59
+ # Build database path - use absolute path to avoid any ambiguity
60
+ _db_file_path = str(Path(APP_DATA_DIRECTORY) / f"ttnn_{DB_VERSION}.db")
61
+ SQLALCHEMY_DATABASE_URI = f"sqlite:///{_db_file_path}"
61
62
  SQLALCHEMY_ENGINE_OPTIONS = {
62
- "pool_size": 10, # Adjust pool size as needed (default is 5)
63
- "max_overflow": 20, # Allow overflow of the pool size if necessary
64
- "pool_timeout": 30, # Timeout in seconds before giving up on getting a connection
63
+ # SQLite-specific settings for multi-process/worker environments
64
+ # NullPool: Each worker gets its own connection, avoiding file locking issues
65
+ # This is critical for gunicorn's multi-worker mode with SQLite
66
+ "poolclass": NullPool,
67
+ "connect_args": {
68
+ "check_same_thread": False, # Allow SQLite to be used across threads in gevent
69
+ },
65
70
  }
66
71
  SQLALCHEMY_TRACK_MODIFICATIONS = False
67
72
 
@@ -1 +1 @@
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-Bixx4Hi-.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};
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-DhooSFaT.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};
@@ -0,0 +1,2 @@
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/allPaths-D6ZQWDaz.js","assets/index-voJy5fZe.js","assets/index-BZITDwoa.js","assets/index-DhooSFaT.js","assets/index-BzMUwfVf.css"])))=>i.map(i=>d[i]);
2
+ import{_ as e}from"./index-DhooSFaT.js";const s=async(t,a)=>{const{getIconPaths:o}=await e(async()=>{const{getIconPaths:r}=await import("./allPaths-D6ZQWDaz.js");return{getIconPaths:r}},__vite__mapDeps([0,1,2,3,4]));return o(t,a)};export{s as allPathsLoader};