ttnn-visualizer 0.36.0__py3-none-any.whl → 0.37.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 +2 -2
- ttnn_visualizer/csv_queries.py +35 -35
- ttnn_visualizer/decorators.py +13 -6
- ttnn_visualizer/file_uploads.py +89 -23
- ttnn_visualizer/{sessions.py → instances.py} +70 -54
- ttnn_visualizer/models.py +1 -1
- ttnn_visualizer/queries.py +26 -26
- ttnn_visualizer/serializers.py +4 -6
- ttnn_visualizer/settings.py +9 -2
- ttnn_visualizer/sftp_operations.py +7 -3
- ttnn_visualizer/static/assets/{allPaths-ChIeDZ5t.js → allPaths-Z03s-OPC.js} +1 -1
- ttnn_visualizer/static/assets/{allPathsLoader-C4OHN8TU.js → allPathsLoader-BnryPsjm.js} +2 -2
- ttnn_visualizer/static/assets/{index-D8zG3DIo.js → index-BgzPx-DB.js} +198 -198
- ttnn_visualizer/static/assets/{index-BnUuxY3c.css → index-je2tF5Bg.css} +1 -1
- ttnn_visualizer/static/assets/{splitPathsBySizeLoader-BL6wqcCx.js → splitPathsBySizeLoader-Ru7hJnSI.js} +1 -1
- ttnn_visualizer/static/index.html +2 -2
- ttnn_visualizer/tests/test_queries.py +28 -28
- ttnn_visualizer/views.py +213 -146
- {ttnn_visualizer-0.36.0.dist-info → ttnn_visualizer-0.37.0.dist-info}/METADATA +1 -1
- {ttnn_visualizer-0.36.0.dist-info → ttnn_visualizer-0.37.0.dist-info}/RECORD +25 -25
- {ttnn_visualizer-0.36.0.dist-info → ttnn_visualizer-0.37.0.dist-info}/LICENSE +0 -0
- {ttnn_visualizer-0.36.0.dist-info → ttnn_visualizer-0.37.0.dist-info}/LICENSE_understanding.txt +0 -0
- {ttnn_visualizer-0.36.0.dist-info → ttnn_visualizer-0.37.0.dist-info}/WHEEL +0 -0
- {ttnn_visualizer-0.36.0.dist-info → ttnn_visualizer-0.37.0.dist-info}/entry_points.txt +0 -0
- {ttnn_visualizer-0.36.0.dist-info → ttnn_visualizer-0.37.0.dist-info}/top_level.txt +0 -0
ttnn_visualizer/views.py
CHANGED
@@ -13,15 +13,15 @@ import shutil
|
|
13
13
|
|
14
14
|
import zstd
|
15
15
|
from flask import Blueprint
|
16
|
-
from flask import
|
16
|
+
from flask import current_app, session, request
|
17
17
|
|
18
18
|
from ttnn_visualizer.csv_queries import DeviceLogProfilerQueries, OpsPerformanceQueries, OpsPerformanceReportQueries
|
19
|
-
from ttnn_visualizer.decorators import
|
19
|
+
from ttnn_visualizer.decorators import with_instance
|
20
20
|
from ttnn_visualizer.enums import ConnectionTestStates
|
21
21
|
from ttnn_visualizer.exceptions import DataFormatError
|
22
22
|
from ttnn_visualizer.exceptions import RemoteConnectionException
|
23
23
|
from ttnn_visualizer.file_uploads import (
|
24
|
-
|
24
|
+
extract_folder_name_from_files,
|
25
25
|
extract_npe_name,
|
26
26
|
save_uploaded_files,
|
27
27
|
validate_files,
|
@@ -43,8 +43,8 @@ from ttnn_visualizer.serializers import (
|
|
43
43
|
serialize_operations_buffers,
|
44
44
|
serialize_devices, serialize_buffer,
|
45
45
|
)
|
46
|
-
from ttnn_visualizer.
|
47
|
-
update_instance,
|
46
|
+
from ttnn_visualizer.instances import (
|
47
|
+
get_instances, update_instance,
|
48
48
|
)
|
49
49
|
from ttnn_visualizer.sftp_operations import (
|
50
50
|
sync_remote_profiler_folders,
|
@@ -69,10 +69,10 @@ api = Blueprint("api", __name__, url_prefix="/api")
|
|
69
69
|
|
70
70
|
|
71
71
|
@api.route("/operations", methods=["GET"])
|
72
|
-
@
|
72
|
+
@with_instance
|
73
73
|
@timer
|
74
|
-
def operation_list(
|
75
|
-
with DatabaseQueries(
|
74
|
+
def operation_list(instance: Instance):
|
75
|
+
with DatabaseQueries(instance) as db:
|
76
76
|
operations = list(db.query_operations())
|
77
77
|
operations.sort(key=lambda o: o.operation_id)
|
78
78
|
operation_arguments = list(db.query_operation_arguments())
|
@@ -98,10 +98,10 @@ def operation_list(session):
|
|
98
98
|
|
99
99
|
|
100
100
|
@api.route("/operations/<operation_id>", methods=["GET"])
|
101
|
-
@
|
101
|
+
@with_instance
|
102
102
|
@timer
|
103
|
-
def operation_detail(operation_id,
|
104
|
-
with DatabaseQueries(
|
103
|
+
def operation_detail(operation_id, instance: Instance):
|
104
|
+
with DatabaseQueries(instance) as db:
|
105
105
|
|
106
106
|
device_id = request.args.get("device_id", None)
|
107
107
|
operations = list(db.query_operations(filters={"operation_id": operation_id}))
|
@@ -171,17 +171,17 @@ def operation_detail(operation_id, session):
|
|
171
171
|
|
172
172
|
|
173
173
|
@api.route("operation-history", methods=["GET"])
|
174
|
-
@
|
174
|
+
@with_instance
|
175
175
|
@timer
|
176
|
-
def operation_history(
|
176
|
+
def operation_history(instance: Instance):
|
177
177
|
operation_history_filename = "operation_history.json"
|
178
|
-
if
|
179
|
-
if not
|
178
|
+
if instance.remote_connection and instance.remote_connection.useRemoteQuerying:
|
179
|
+
if not instance.remote_folder:
|
180
180
|
return []
|
181
181
|
operation_history = read_remote_file(
|
182
|
-
remote_connection=
|
182
|
+
remote_connection=instance.remote_connection,
|
183
183
|
remote_path=Path(
|
184
|
-
|
184
|
+
instance.remote_folder.remotePath, operation_history_filename
|
185
185
|
),
|
186
186
|
)
|
187
187
|
if not operation_history:
|
@@ -189,7 +189,7 @@ def operation_history(session: Instance):
|
|
189
189
|
return json.loads(operation_history)
|
190
190
|
else:
|
191
191
|
operation_history_file = (
|
192
|
-
Path(str(
|
192
|
+
Path(str(instance.profiler_path)).parent / operation_history_filename
|
193
193
|
)
|
194
194
|
if not operation_history_file.exists():
|
195
195
|
return []
|
@@ -198,21 +198,21 @@ def operation_history(session: Instance):
|
|
198
198
|
|
199
199
|
|
200
200
|
@api.route("/config")
|
201
|
-
@
|
201
|
+
@with_instance
|
202
202
|
@timer
|
203
|
-
def get_config(
|
204
|
-
if
|
205
|
-
if not
|
203
|
+
def get_config(instance: Instance):
|
204
|
+
if instance.remote_connection and instance.remote_connection.useRemoteQuerying:
|
205
|
+
if not instance.remote_profiler_folder:
|
206
206
|
return {}
|
207
207
|
config = read_remote_file(
|
208
|
-
remote_connection=
|
209
|
-
remote_path=Path(
|
208
|
+
remote_connection=instance.remote_connection,
|
209
|
+
remote_path=Path(instance.remote_profiler_folder.remotePath, "config.json"),
|
210
210
|
)
|
211
211
|
if not config:
|
212
212
|
return {}
|
213
213
|
return config
|
214
214
|
else:
|
215
|
-
config_file = Path(str(
|
215
|
+
config_file = Path(str(instance.profiler_path)).parent.joinpath("config.json")
|
216
216
|
if not config_file.exists():
|
217
217
|
return {}
|
218
218
|
with open(config_file, "r") as file:
|
@@ -220,10 +220,10 @@ def get_config(session: Instance):
|
|
220
220
|
|
221
221
|
|
222
222
|
@api.route("/tensors", methods=["GET"])
|
223
|
-
@
|
223
|
+
@with_instance
|
224
224
|
@timer
|
225
|
-
def tensors_list(
|
226
|
-
with DatabaseQueries(
|
225
|
+
def tensors_list(instance: Instance):
|
226
|
+
with DatabaseQueries(instance) as db:
|
227
227
|
device_id = request.args.get("device_id", None)
|
228
228
|
tensors = list(db.query_tensors(filters={"device_id": device_id}))
|
229
229
|
local_comparisons = list(db.query_tensor_comparisons())
|
@@ -235,9 +235,9 @@ def tensors_list(session: Instance):
|
|
235
235
|
|
236
236
|
|
237
237
|
@api.route("/buffer", methods=["GET"])
|
238
|
-
@
|
238
|
+
@with_instance
|
239
239
|
@timer
|
240
|
-
def buffer_detail(
|
240
|
+
def buffer_detail(instance: Instance):
|
241
241
|
address = request.args.get("address")
|
242
242
|
operation_id = request.args.get("operation_id")
|
243
243
|
|
@@ -249,7 +249,7 @@ def buffer_detail(session: Instance):
|
|
249
249
|
else:
|
250
250
|
return Response(status=HTTPStatus.BAD_REQUEST)
|
251
251
|
|
252
|
-
with DatabaseQueries(
|
252
|
+
with DatabaseQueries(instance) as db:
|
253
253
|
buffer = db.query_next_buffer(operation_id, address)
|
254
254
|
if not buffer:
|
255
255
|
return Response(status=HTTPStatus.NOT_FOUND)
|
@@ -257,9 +257,9 @@ def buffer_detail(session: Instance):
|
|
257
257
|
|
258
258
|
|
259
259
|
@api.route("/buffer-pages", methods=["GET"])
|
260
|
-
@
|
260
|
+
@with_instance
|
261
261
|
@timer
|
262
|
-
def buffer_pages(
|
262
|
+
def buffer_pages(instance: Instance):
|
263
263
|
address = request.args.get("address")
|
264
264
|
operation_id = request.args.get("operation_id")
|
265
265
|
buffer_type = request.args.get("buffer_type", "")
|
@@ -275,7 +275,7 @@ def buffer_pages(session: Instance):
|
|
275
275
|
else:
|
276
276
|
buffer_type = None
|
277
277
|
|
278
|
-
with DatabaseQueries(
|
278
|
+
with DatabaseQueries(instance) as db:
|
279
279
|
buffers = list(
|
280
280
|
list(
|
281
281
|
db.query_buffer_pages(
|
@@ -292,10 +292,10 @@ def buffer_pages(session: Instance):
|
|
292
292
|
|
293
293
|
|
294
294
|
@api.route("/tensors/<tensor_id>", methods=["GET"])
|
295
|
-
@
|
295
|
+
@with_instance
|
296
296
|
@timer
|
297
|
-
def tensor_detail(tensor_id,
|
298
|
-
with DatabaseQueries(
|
297
|
+
def tensor_detail(tensor_id, instance: Instance):
|
298
|
+
with DatabaseQueries(instance) as db:
|
299
299
|
tensors = list(db.query_tensors(filters={"tensor_id": tensor_id}))
|
300
300
|
if not tensors:
|
301
301
|
return Response(status=HTTPStatus.NOT_FOUND)
|
@@ -304,8 +304,8 @@ def tensor_detail(tensor_id, session: Instance):
|
|
304
304
|
|
305
305
|
|
306
306
|
@api.route("/buffers", methods=["GET"])
|
307
|
-
@
|
308
|
-
def get_all_buffers(
|
307
|
+
@with_instance
|
308
|
+
def get_all_buffers(instance: Instance):
|
309
309
|
buffer_type = request.args.get("buffer_type", "")
|
310
310
|
device_id = request.args.get("device_id", None)
|
311
311
|
if buffer_type and str.isdigit(buffer_type):
|
@@ -313,7 +313,7 @@ def get_all_buffers(session: Instance):
|
|
313
313
|
else:
|
314
314
|
buffer_type = None
|
315
315
|
|
316
|
-
with DatabaseQueries(
|
316
|
+
with DatabaseQueries(instance) as db:
|
317
317
|
buffers = list(
|
318
318
|
db.query_buffers(
|
319
319
|
filters={"buffer_type": buffer_type, "device_id": device_id}
|
@@ -324,8 +324,8 @@ def get_all_buffers(session: Instance):
|
|
324
324
|
|
325
325
|
|
326
326
|
@api.route("/operation-buffers", methods=["GET"])
|
327
|
-
@
|
328
|
-
def get_operations_buffers(
|
327
|
+
@with_instance
|
328
|
+
def get_operations_buffers(instance: Instance):
|
329
329
|
buffer_type = request.args.get("buffer_type", "")
|
330
330
|
device_id = request.args.get("device_id", None)
|
331
331
|
if buffer_type and str.isdigit(buffer_type):
|
@@ -333,7 +333,7 @@ def get_operations_buffers(session: Instance):
|
|
333
333
|
else:
|
334
334
|
buffer_type = None
|
335
335
|
|
336
|
-
with DatabaseQueries(
|
336
|
+
with DatabaseQueries(instance) as db:
|
337
337
|
buffers = list(
|
338
338
|
db.query_buffers(
|
339
339
|
filters={"buffer_type": buffer_type, "device_id": device_id}
|
@@ -344,8 +344,8 @@ def get_operations_buffers(session: Instance):
|
|
344
344
|
|
345
345
|
|
346
346
|
@api.route("/operation-buffers/<operation_id>", methods=["GET"])
|
347
|
-
@
|
348
|
-
def get_operation_buffers(operation_id,
|
347
|
+
@with_instance
|
348
|
+
def get_operation_buffers(operation_id, instance: Instance):
|
349
349
|
buffer_type = request.args.get("buffer_type", "")
|
350
350
|
device_id = request.args.get("device_id", None)
|
351
351
|
if buffer_type and str.isdigit(buffer_type):
|
@@ -353,7 +353,7 @@ def get_operation_buffers(operation_id, session: Instance):
|
|
353
353
|
else:
|
354
354
|
buffer_type = None
|
355
355
|
|
356
|
-
with DatabaseQueries(
|
356
|
+
with DatabaseQueries(instance) as db:
|
357
357
|
operations = list(db.query_operations(filters={"operation_id": operation_id}))
|
358
358
|
if not operations:
|
359
359
|
return Response(status=HTTPStatus.NOT_FOUND)
|
@@ -373,16 +373,16 @@ def get_operation_buffers(operation_id, session: Instance):
|
|
373
373
|
|
374
374
|
|
375
375
|
@api.route("/profiler", methods=["GET"])
|
376
|
-
@
|
377
|
-
def get_profiler_data_list(
|
376
|
+
@with_instance
|
377
|
+
def get_profiler_data_list(instance: Instance):
|
378
378
|
# Doesn't handle remote at the moment
|
379
|
-
# is_remote = True if
|
379
|
+
# is_remote = True if instance.remote_connection else False
|
380
380
|
# config_key = "REMOTE_DATA_DIRECTORY" if is_remote else "LOCAL_DATA_DIRECTORY"
|
381
381
|
config_key = 'LOCAL_DATA_DIRECTORY'
|
382
382
|
data_directory = Path(current_app.config[config_key])
|
383
383
|
|
384
384
|
# if is_remote:
|
385
|
-
# connection = RemoteConnection.model_validate(
|
385
|
+
# connection = RemoteConnection.model_validate(instance.remote_connection, strict=False)
|
386
386
|
# path = data_directory / connection.host / current_app.config["PROFILER_DIRECTORY_NAME"]
|
387
387
|
# else:
|
388
388
|
path = data_directory / current_app.config["PROFILER_DIRECTORY_NAME"]
|
@@ -390,13 +390,38 @@ def get_profiler_data_list(session: Instance):
|
|
390
390
|
if not path.exists():
|
391
391
|
path.mkdir(parents=True, exist_ok=True)
|
392
392
|
|
393
|
-
directory_names = [directory.name for directory in path.iterdir() if directory.is_dir()]
|
394
|
-
|
395
393
|
valid_dirs = []
|
396
394
|
|
395
|
+
if current_app.config["SERVER_MODE"]:
|
396
|
+
session_instances = session.get("instances", [])
|
397
|
+
instances = get_instances(session_instances)
|
398
|
+
db_paths = [instance.profiler_path for instance in instances if instance.profiler_path]
|
399
|
+
directory_names = [str(Path(db_path).parent.name) for db_path in db_paths]
|
400
|
+
else:
|
401
|
+
directory_names = [directory.name for directory in path.iterdir() if directory.is_dir()]
|
402
|
+
|
403
|
+
# Sort directory names by modified time (most recent first)
|
404
|
+
def get_modified_time(dir_name):
|
405
|
+
dir_path = Path(path) / dir_name
|
406
|
+
if dir_path.exists():
|
407
|
+
return dir_path.stat().st_mtime
|
408
|
+
return 0
|
409
|
+
|
410
|
+
directory_names.sort(key=get_modified_time, reverse=True)
|
411
|
+
|
397
412
|
for dir_name in directory_names:
|
398
413
|
dir_path = Path(path) / dir_name
|
399
414
|
files = list(dir_path.glob("**/*"))
|
415
|
+
report_name = None
|
416
|
+
config_file = dir_path / "config.json"
|
417
|
+
|
418
|
+
if config_file.exists():
|
419
|
+
try:
|
420
|
+
with open(config_file, "r") as f:
|
421
|
+
config_data = json.load(f)
|
422
|
+
report_name = config_data.get("report_name")
|
423
|
+
except Exception as e:
|
424
|
+
logger.warning(f"Failed to read config.json in {dir_path}: {e}")
|
400
425
|
|
401
426
|
# Would like to use the existing validate_files function but there's a type difference I'm not sure how to handle
|
402
427
|
if not any(file.name == "db.sqlite" for file in files):
|
@@ -404,15 +429,15 @@ def get_profiler_data_list(session: Instance):
|
|
404
429
|
if not any(file.name == "config.json" for file in files):
|
405
430
|
continue
|
406
431
|
|
407
|
-
valid_dirs.append(
|
432
|
+
valid_dirs.append({"path": dir_path.name, "reportName": report_name})
|
408
433
|
|
409
434
|
return jsonify(valid_dirs)
|
410
435
|
|
411
436
|
|
412
437
|
@api.route("/profiler/<profiler_name>", methods=["DELETE"])
|
413
|
-
@
|
414
|
-
def delete_profiler_report(profiler_name,
|
415
|
-
is_remote = bool(
|
438
|
+
@with_instance
|
439
|
+
def delete_profiler_report(profiler_name, instance: Instance):
|
440
|
+
is_remote = bool(instance.remote_connection)
|
416
441
|
config_key = "REMOTE_DATA_DIRECTORY" if is_remote else "LOCAL_DATA_DIRECTORY"
|
417
442
|
data_directory = Path(current_app.config[config_key])
|
418
443
|
|
@@ -420,12 +445,12 @@ def delete_profiler_report(profiler_name, session: Instance):
|
|
420
445
|
return Response(status=HTTPStatus.BAD_REQUEST, response="Report name is required.")
|
421
446
|
|
422
447
|
if is_remote:
|
423
|
-
connection = RemoteConnection.model_validate(
|
448
|
+
connection = RemoteConnection.model_validate(instance.remote_connection, strict=False)
|
424
449
|
path = data_directory / connection.host / current_app.config["PROFILER_DIRECTORY_NAME"]
|
425
450
|
else:
|
426
451
|
path = data_directory / current_app.config["PROFILER_DIRECTORY_NAME"] / profiler_name
|
427
452
|
|
428
|
-
if
|
453
|
+
if instance.active_report and instance.active_report.profiler_name == profiler_name:
|
429
454
|
instance_id = request.args.get("instanceId")
|
430
455
|
update_instance(instance_id=instance_id,profiler_name="")
|
431
456
|
|
@@ -439,26 +464,38 @@ def delete_profiler_report(profiler_name, session: Instance):
|
|
439
464
|
|
440
465
|
|
441
466
|
@api.route("/performance", methods=["GET"])
|
442
|
-
@
|
443
|
-
def get_performance_data_list(
|
444
|
-
is_remote = True if
|
467
|
+
@with_instance
|
468
|
+
def get_performance_data_list(instance: Instance):
|
469
|
+
is_remote = True if instance.remote_connection else False
|
445
470
|
config_key = "REMOTE_DATA_DIRECTORY" if is_remote else "LOCAL_DATA_DIRECTORY"
|
446
|
-
config_key = 'LOCAL_DATA_DIRECTORY'
|
447
471
|
data_directory = Path(current_app.config[config_key])
|
472
|
+
path = data_directory / current_app.config["PERFORMANCE_DIRECTORY_NAME"]
|
448
473
|
|
449
|
-
if is_remote:
|
450
|
-
connection = RemoteConnection.model_validate(session.remote_connection, strict=False)
|
451
|
-
path = data_directory / connection.host / current_app.config["PERFORMANCE_DIRECTORY_NAME"]
|
452
|
-
else:
|
453
|
-
path = data_directory / current_app.config["PERFORMANCE_DIRECTORY_NAME"]
|
454
|
-
|
455
|
-
if not path.exists():
|
474
|
+
if not is_remote and not path.exists():
|
456
475
|
path.mkdir(parents=True, exist_ok=True)
|
457
476
|
|
458
|
-
|
477
|
+
if current_app.config["SERVER_MODE"]:
|
478
|
+
session_instances = session.get("instances", [])
|
479
|
+
instances = get_instances(session_instances)
|
480
|
+
db_paths = [instance.performance_path for instance in instances if instance.performance_path]
|
481
|
+
directory_names = [str(Path(db_path).name) for db_path in db_paths]
|
482
|
+
else:
|
483
|
+
if is_remote:
|
484
|
+
connection = RemoteConnection.model_validate(instance.remote_connection, strict=False)
|
485
|
+
path = data_directory / connection.host / current_app.config["PERFORMANCE_DIRECTORY_NAME"]
|
486
|
+
directory_names = [directory.name for directory in path.iterdir() if directory.is_dir()]
|
459
487
|
|
460
488
|
valid_dirs = []
|
461
489
|
|
490
|
+
# Sort directory names by modified time (most recent first)
|
491
|
+
def get_modified_time(dir_name):
|
492
|
+
dir_path = Path(path) / dir_name
|
493
|
+
if dir_path.exists():
|
494
|
+
return dir_path.stat().st_mtime
|
495
|
+
return 0
|
496
|
+
|
497
|
+
directory_names.sort(key=get_modified_time, reverse=True)
|
498
|
+
|
462
499
|
for dir_name in directory_names:
|
463
500
|
dir_path = Path(path) / dir_name
|
464
501
|
files = list(dir_path.glob("**/*"))
|
@@ -471,36 +508,36 @@ def get_performance_data_list(session: Instance):
|
|
471
508
|
if not any(file.name.startswith("ops_perf_results") for file in files):
|
472
509
|
continue
|
473
510
|
|
474
|
-
valid_dirs.append(
|
511
|
+
valid_dirs.append({"path": dir_path.name, "reportName": dir_path.name})
|
475
512
|
|
476
513
|
return jsonify(valid_dirs)
|
477
514
|
|
478
515
|
|
479
516
|
@api.route("/performance/device-log", methods=["GET"])
|
480
|
-
@
|
481
|
-
def get_performance_data(
|
482
|
-
if not
|
517
|
+
@with_instance
|
518
|
+
def get_performance_data(instance: Instance):
|
519
|
+
if not instance.performance_path:
|
483
520
|
return Response(status=HTTPStatus.NOT_FOUND)
|
484
|
-
with DeviceLogProfilerQueries(
|
521
|
+
with DeviceLogProfilerQueries(instance) as csv:
|
485
522
|
result = csv.get_all_entries(as_dict=True, limit=100)
|
486
523
|
return jsonify(result)
|
487
524
|
|
488
525
|
|
489
526
|
@api.route("/performance/perf-results", methods=["GET"])
|
490
|
-
@
|
491
|
-
def get_profiler_performance_data(
|
492
|
-
if not
|
527
|
+
@with_instance
|
528
|
+
def get_profiler_performance_data(instance: Instance):
|
529
|
+
if not instance.performance_path:
|
493
530
|
return Response(status=HTTPStatus.NOT_FOUND)
|
494
|
-
with OpsPerformanceQueries(
|
531
|
+
with OpsPerformanceQueries(instance) as csv:
|
495
532
|
# result = csv.query_by_op_code(op_code="(torch) contiguous", as_dict=True)
|
496
533
|
result = csv.get_all_entries(as_dict=True, limit=100)
|
497
534
|
return jsonify(result)
|
498
535
|
|
499
536
|
|
500
537
|
@api.route("/performance/<performance_name>", methods=["DELETE"])
|
501
|
-
@
|
502
|
-
def delete_performance_report(performance_name,
|
503
|
-
is_remote = bool(
|
538
|
+
@with_instance
|
539
|
+
def delete_performance_report(performance_name, instance: Instance):
|
540
|
+
is_remote = bool(instance.remote_connection)
|
504
541
|
config_key = "REMOTE_DATA_DIRECTORY" if is_remote else "LOCAL_DATA_DIRECTORY"
|
505
542
|
data_directory = Path(current_app.config[config_key])
|
506
543
|
|
@@ -508,12 +545,12 @@ def delete_performance_report(performance_name, session: Instance):
|
|
508
545
|
return Response(status=HTTPStatus.BAD_REQUEST, response="Report name is required.")
|
509
546
|
|
510
547
|
if is_remote:
|
511
|
-
connection = RemoteConnection.model_validate(
|
548
|
+
connection = RemoteConnection.model_validate(instance.remote_connection, strict=False)
|
512
549
|
path = data_directory / connection.host / current_app.config["PERFORMANCE_DIRECTORY_NAME"]
|
513
550
|
else:
|
514
551
|
path = data_directory / current_app.config["PERFORMANCE_DIRECTORY_NAME"] / performance_name
|
515
552
|
|
516
|
-
if
|
553
|
+
if instance.active_report and instance.active_report.performance_name == performance_name:
|
517
554
|
instance_id = request.args.get("instanceId")
|
518
555
|
update_instance(instance_id=instance_id,performance_name="")
|
519
556
|
|
@@ -526,11 +563,11 @@ def delete_performance_report(performance_name, session: Instance):
|
|
526
563
|
|
527
564
|
|
528
565
|
@api.route("/performance/perf-results/raw", methods=["GET"])
|
529
|
-
@
|
530
|
-
def get_performance_results_data_raw(
|
531
|
-
if not
|
566
|
+
@with_instance
|
567
|
+
def get_performance_results_data_raw(instance: Instance):
|
568
|
+
if not instance.performance_path:
|
532
569
|
return Response(status=HTTPStatus.NOT_FOUND)
|
533
|
-
content = OpsPerformanceQueries.get_raw_csv(
|
570
|
+
content = OpsPerformanceQueries.get_raw_csv(instance)
|
534
571
|
return Response(
|
535
572
|
content,
|
536
573
|
mimetype="text/csv",
|
@@ -539,20 +576,20 @@ def get_performance_results_data_raw(session: Instance):
|
|
539
576
|
|
540
577
|
|
541
578
|
@api.route("/performance/perf-results/report", methods=["GET"])
|
542
|
-
@
|
543
|
-
def get_performance_results_report(
|
544
|
-
if not
|
579
|
+
@with_instance
|
580
|
+
def get_performance_results_report(instance: Instance):
|
581
|
+
if not instance.performance_path:
|
545
582
|
return Response(status=HTTPStatus.NOT_FOUND)
|
546
583
|
|
547
584
|
name = request.args.get("name", None)
|
548
|
-
performance_path = Path(
|
585
|
+
performance_path = Path(instance.performance_path)
|
549
586
|
if name:
|
550
587
|
performance_path = performance_path.parent / name
|
551
|
-
|
552
|
-
logger.info(f"************
|
588
|
+
instance.performance_path = str(performance_path)
|
589
|
+
logger.info(f"************ Performance path set to {instance.performance_path}")
|
553
590
|
|
554
591
|
try:
|
555
|
-
report = OpsPerformanceReportQueries.generate_report(
|
592
|
+
report = OpsPerformanceReportQueries.generate_report(instance)
|
556
593
|
except DataFormatError:
|
557
594
|
return Response(status=HTTPStatus.UNPROCESSABLE_ENTITY)
|
558
595
|
|
@@ -560,11 +597,11 @@ def get_performance_results_report(session: Instance):
|
|
560
597
|
|
561
598
|
|
562
599
|
@api.route("/performance/device-log/raw", methods=["GET"])
|
563
|
-
@
|
564
|
-
def get_performance_data_raw(
|
565
|
-
if not
|
600
|
+
@with_instance
|
601
|
+
def get_performance_data_raw(instance: Instance):
|
602
|
+
if not instance.performance_path:
|
566
603
|
return Response(status=HTTPStatus.NOT_FOUND)
|
567
|
-
content = DeviceLogProfilerQueries.get_raw_csv(
|
604
|
+
content = DeviceLogProfilerQueries.get_raw_csv(instance)
|
568
605
|
return Response(
|
569
606
|
content,
|
570
607
|
mimetype="text/csv",
|
@@ -573,19 +610,19 @@ def get_performance_data_raw(session: Instance):
|
|
573
610
|
|
574
611
|
|
575
612
|
@api.route("/performance/device-log/zone/<zone>", methods=["GET"])
|
576
|
-
@
|
577
|
-
def get_zone_statistics(zone,
|
578
|
-
if not
|
613
|
+
@with_instance
|
614
|
+
def get_zone_statistics(zone, instance: Instance):
|
615
|
+
if not instance.performance_path:
|
579
616
|
return Response(status=HTTPStatus.NOT_FOUND)
|
580
|
-
with DeviceLogProfilerQueries(
|
617
|
+
with DeviceLogProfilerQueries(instance) as csv:
|
581
618
|
result = csv.query_zone_statistics(zone_name=zone, as_dict=True)
|
582
619
|
return jsonify(result)
|
583
620
|
|
584
621
|
|
585
622
|
@api.route("/devices", methods=["GET"])
|
586
|
-
@
|
587
|
-
def get_devices(
|
588
|
-
with DatabaseQueries(
|
623
|
+
@with_instance
|
624
|
+
def get_devices(instance: Instance):
|
625
|
+
with DatabaseQueries(instance) as db:
|
589
626
|
devices = list(db.query_devices())
|
590
627
|
return serialize_devices(devices)
|
591
628
|
|
@@ -593,7 +630,7 @@ def get_devices(session: Instance):
|
|
593
630
|
@api.route("/local/upload/profiler", methods=["POST"])
|
594
631
|
def create_profiler_files():
|
595
632
|
files = request.files.getlist("files")
|
596
|
-
folder_name = request.form.get("folderName") # Optional folder name
|
633
|
+
folder_name = request.form.get("folderName") # Optional folder name - Used for Safari compatibility
|
597
634
|
profiler_directory = current_app.config["LOCAL_DATA_DIRECTORY"] / current_app.config["PROFILER_DIRECTORY_NAME"]
|
598
635
|
|
599
636
|
if not validate_files(files, {"db.sqlite", "config.json"}, folder_name=folder_name):
|
@@ -606,21 +643,35 @@ def create_profiler_files():
|
|
606
643
|
profiler_directory.mkdir(parents=True, exist_ok=True)
|
607
644
|
|
608
645
|
if folder_name:
|
609
|
-
|
646
|
+
parent_folder_name = folder_name
|
610
647
|
else:
|
611
|
-
|
648
|
+
parent_folder_name = extract_folder_name_from_files(files)
|
612
649
|
|
613
|
-
logger.info(f"Writing report files to {profiler_directory}/{
|
650
|
+
logger.info(f"Writing report files to {profiler_directory}/{parent_folder_name}")
|
614
651
|
|
615
|
-
|
652
|
+
try:
|
653
|
+
save_uploaded_files(files, profiler_directory, folder_name)
|
654
|
+
except DataFormatError:
|
655
|
+
return Response(status=HTTPStatus.UNPROCESSABLE_ENTITY)
|
616
656
|
|
617
657
|
instance_id = request.args.get("instanceId")
|
618
|
-
update_instance(instance_id=instance_id, profiler_name=
|
658
|
+
update_instance(instance_id=instance_id, profiler_name=parent_folder_name, clear_remote=True)
|
619
659
|
|
620
|
-
|
621
|
-
|
622
|
-
).model_dump()
|
660
|
+
config_file = profiler_directory / parent_folder_name / "config.json"
|
661
|
+
report_name = None
|
623
662
|
|
663
|
+
if config_file.exists():
|
664
|
+
try:
|
665
|
+
with open(config_file, "r") as f:
|
666
|
+
config_data = json.load(f)
|
667
|
+
report_name = config_data.get("report_name")
|
668
|
+
except Exception as e:
|
669
|
+
logger.warning(f"Failed to read config.json in {config_file}: {e}")
|
670
|
+
|
671
|
+
return {
|
672
|
+
"path": parent_folder_name,
|
673
|
+
"reportName": report_name,
|
674
|
+
}
|
624
675
|
|
625
676
|
@api.route("/local/upload/performance", methods=["POST"])
|
626
677
|
def create_profile_files():
|
@@ -645,21 +696,24 @@ def create_profile_files():
|
|
645
696
|
target_directory.mkdir(parents=True, exist_ok=True)
|
646
697
|
|
647
698
|
if folder_name:
|
648
|
-
|
699
|
+
parent_folder_name = folder_name
|
649
700
|
else:
|
650
|
-
|
701
|
+
parent_folder_name = extract_folder_name_from_files(files)
|
651
702
|
|
652
|
-
logger.info(f"Writing performance files to {target_directory}/{
|
703
|
+
logger.info(f"Writing performance files to {target_directory}/{parent_folder_name}")
|
653
704
|
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
705
|
+
try:
|
706
|
+
save_uploaded_files(
|
707
|
+
files,
|
708
|
+
target_directory,
|
709
|
+
folder_name
|
710
|
+
)
|
711
|
+
except DataFormatError:
|
712
|
+
return Response(status=HTTPStatus.UNPROCESSABLE_ENTITY)
|
659
713
|
|
660
714
|
instance_id = request.args.get("instanceId")
|
661
715
|
update_instance(
|
662
|
-
instance_id=instance_id, performance_name=
|
716
|
+
instance_id=instance_id, performance_name=parent_folder_name, clear_remote=True
|
663
717
|
)
|
664
718
|
|
665
719
|
return StatusMessage(
|
@@ -683,7 +737,10 @@ def create_npe_files():
|
|
683
737
|
target_directory = data_directory / current_app.config["NPE_DIRECTORY_NAME"]
|
684
738
|
target_directory.mkdir(parents=True, exist_ok=True)
|
685
739
|
|
686
|
-
|
740
|
+
try:
|
741
|
+
save_uploaded_files(files, target_directory)
|
742
|
+
except DataFormatError:
|
743
|
+
return Response(status=HTTPStatus.UNPROCESSABLE_ENTITY)
|
687
744
|
|
688
745
|
instance_id = request.args.get("instanceId")
|
689
746
|
update_instance(instance_id=instance_id, npe_name=npe_name, clear_remote=True)
|
@@ -746,11 +803,11 @@ import yaml
|
|
746
803
|
|
747
804
|
|
748
805
|
@api.route("/cluster-descriptor", methods=["GET"])
|
749
|
-
@
|
750
|
-
def get_cluster_descriptor(
|
751
|
-
if
|
806
|
+
@with_instance
|
807
|
+
def get_cluster_descriptor(instance: Instance):
|
808
|
+
if instance.remote_connection:
|
752
809
|
try:
|
753
|
-
cluster_desc_file = get_cluster_desc(
|
810
|
+
cluster_desc_file = get_cluster_desc(instance.remote_connection)
|
754
811
|
if not cluster_desc_file:
|
755
812
|
return jsonify({"error": "cluster_descriptor.yaml not found"}), 404
|
756
813
|
yaml_data = yaml.safe_load(cluster_desc_file.decode("utf-8"))
|
@@ -765,7 +822,7 @@ def get_cluster_descriptor(session: Instance):
|
|
765
822
|
except Exception as e:
|
766
823
|
return jsonify({"error": f"An unexpected error occurred: {str(e)}"}), 500
|
767
824
|
else:
|
768
|
-
local_path = get_cluster_descriptor_path(
|
825
|
+
local_path = get_cluster_descriptor_path(instance)
|
769
826
|
|
770
827
|
if not local_path:
|
771
828
|
return jsonify({"error": "cluster_descriptor.yaml not found"}), 404
|
@@ -942,11 +999,12 @@ def use_remote_folder():
|
|
942
999
|
remote_performance_folder = None
|
943
1000
|
if profile:
|
944
1001
|
remote_performance_folder = RemoteReportFolder.model_validate(profile, strict=False)
|
945
|
-
performance_name = remote_performance_folder.
|
1002
|
+
performance_name = remote_performance_folder.reportName
|
946
1003
|
data_directory = current_app.config["REMOTE_DATA_DIRECTORY"]
|
947
|
-
profiler_name =
|
1004
|
+
profiler_name = folder.reportName
|
1005
|
+
folder_name = folder.remotePath.split("/")[-1]
|
948
1006
|
|
949
|
-
connection_directory = Path(data_directory, connection.host, current_app.config["PROFILER_DIRECTORY_NAME"],
|
1007
|
+
connection_directory = Path(data_directory, connection.host, current_app.config["PROFILER_DIRECTORY_NAME"], folder_name)
|
950
1008
|
|
951
1009
|
if not connection.useRemoteQuerying and not connection_directory.exists():
|
952
1010
|
return Response(
|
@@ -976,14 +1034,14 @@ def health_check():
|
|
976
1034
|
return Response(status=HTTPStatus.OK)
|
977
1035
|
|
978
1036
|
|
979
|
-
@api.route("/
|
980
|
-
@
|
981
|
-
def get_instance(
|
1037
|
+
@api.route("/instance", methods=["GET"])
|
1038
|
+
@with_instance
|
1039
|
+
def get_instance(instance: Instance):
|
982
1040
|
# Used to gate UI functions if no report is active
|
983
|
-
return
|
1041
|
+
return instance.model_dump()
|
984
1042
|
|
985
1043
|
|
986
|
-
@api.route("/
|
1044
|
+
@api.route("/instance", methods=["PUT"])
|
987
1045
|
def update_current_instance():
|
988
1046
|
try:
|
989
1047
|
update_data = request.get_json()
|
@@ -1004,24 +1062,24 @@ def update_current_instance():
|
|
1004
1062
|
|
1005
1063
|
return Response(status=HTTPStatus.OK)
|
1006
1064
|
except Exception as e:
|
1007
|
-
logger.error(f"Error updating
|
1065
|
+
logger.error(f"Error updating instance: {str(e)}")
|
1008
1066
|
|
1009
1067
|
return Response(
|
1010
1068
|
status=HTTPStatus.INTERNAL_SERVER_ERROR,
|
1011
|
-
response="An error occurred while updating the
|
1069
|
+
response="An error occurred while updating the instance.",
|
1012
1070
|
)
|
1013
1071
|
|
1014
1072
|
|
1015
1073
|
@api.route("/npe", methods=["GET"])
|
1016
|
-
@
|
1074
|
+
@with_instance
|
1017
1075
|
@timer
|
1018
|
-
def get_npe_data(
|
1019
|
-
if not
|
1020
|
-
logger.error("NPE path is not set in the
|
1076
|
+
def get_npe_data(instance: Instance):
|
1077
|
+
if not instance.npe_path:
|
1078
|
+
logger.error("NPE path is not set in the instance.")
|
1021
1079
|
return Response(status=HTTPStatus.NOT_FOUND)
|
1022
1080
|
|
1023
|
-
compressed_path = Path(f"{
|
1024
|
-
uncompressed_path = Path(f"{
|
1081
|
+
compressed_path = Path(f"{instance.npe_path}/{instance.active_report.npe_name}.npeviz.zst")
|
1082
|
+
uncompressed_path = Path(f"{instance.npe_path}/{instance.active_report.npe_name}.json")
|
1025
1083
|
|
1026
1084
|
if not compressed_path.exists() and not uncompressed_path.exists():
|
1027
1085
|
logger.error(f"NPE file does not exist: {compressed_path} / {uncompressed_path}")
|
@@ -1037,3 +1095,12 @@ def get_npe_data(session: Instance):
|
|
1037
1095
|
npe_data = json.load(file)
|
1038
1096
|
|
1039
1097
|
return jsonify(npe_data)
|
1098
|
+
|
1099
|
+
|
1100
|
+
@api.route("/config.js", methods=["GET"])
|
1101
|
+
def config_js():
|
1102
|
+
config = {
|
1103
|
+
"SERVER_MODE": current_app.config["SERVER_MODE"],
|
1104
|
+
}
|
1105
|
+
js = f"window.TTNN_VISUALIZER_CONFIG = {json.dumps(config)};"
|
1106
|
+
return Response(js, mimetype="application/javascript")
|