diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index eac11d1a..180f5424 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -1,49 +1,81 @@ -name: Build wheel - +name: Build and Upload Python Wheel on Release on: - release: - types: [ 'created', 'edited', 'unpublished' ] + release: + types: [released, prereleased ] env: CIBW_BUILD_VERBOSITY: 1 VITE_API_ROOT: api + NPM_CONFIG_PRODUCTION: false jobs: - build_wheel: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - - name: Use Node.js - uses: actions/setup-node@v3 - with: - node-version: v22.1.0 - - run: npm install - - run: npm run build - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: 3.9 - - - name: Build wheel and install - run: | - python -m pip install --user --upgrade build - python -m build --wheel - - - uses: actions/upload-artifact@v4 - with: - name: ttnn_visualizer_${{ github.sha }} - path: ./dist/*.whl - - upload-wheel-release: - runs-on: ubuntu-20.04 - needs: - build_wheel - steps: - - name: '📦 Upload Release' - run: | - cd ${{github.workspace}} - gh release upload ${{github.event.release.tag_name}} ttnn_visualizer_${{github.sha}} - env: - GITHUB_TOKEN: ${{ github.TOKEN }} - shell: bash + build: + name: Build Python Wheel + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: v22.1.0 + - run: npm install + - run: npm run build + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.12.3' + + - name: Install build dependencies + run: | + python -m pip install --upgrade pip + pip install build + + - name: Build wheel + run: | + python -m build --wheel + + - name: Upload wheel as artifact + uses: actions/upload-artifact@v3 + with: + name: python-wheel + path: dist/*.whl + + release: + name: Upload Wheel to GitHub Release + runs-on: ubuntu-latest + needs: build # Depends on the build job + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Download wheel artifact + uses: actions/download-artifact@v3 + with: + name: python-wheel + path: /home/runner/work/ttnn-visualizer/ + - name: get-npm-version + id: package-version + uses: martinbeentjes/npm-get-version-action@v1.3.1 + + - name: Get wheel file name + id: get_wheel + run: | + # Find the .whl file and store its name in a variable + FILE=$(find /home/runner/work/ttnn-visualizer/ -name "*.whl" -type f) + echo "Found wheel file: $FILE" + # Set output to the found file name + echo "wheel_name=$FILE" >> $GITHUB_ENV + + - name: Upload Wheel to Release + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ github.event.release.upload_url }} + asset_path: ${{ env.wheel_name }} + asset_name: ttnn-visualizer-${{ github.event.release.tag_name }}.whl + asset_content_type: application/octet-stream diff --git a/backend/ttnn_visualizer/app.py b/backend/ttnn_visualizer/app.py index b9b2396a..4dc55778 100644 --- a/backend/ttnn_visualizer/app.py +++ b/backend/ttnn_visualizer/app.py @@ -70,7 +70,7 @@ def catch_all(path): def extensions(app: flask.Flask): - from ttnn_visualizer.extensions import flask_static_digest, db, ma + from ttnn_visualizer.extensions import flask_static_digest """ Register 0 or more extensions (mutates the app passed in). @@ -79,8 +79,6 @@ def extensions(app: flask.Flask): :return: None """ - db.init_app(app) - ma.init_app(app) flask_static_digest.init_app(app) # For automatically reflecting table data diff --git a/backend/ttnn_visualizer/extensions.py b/backend/ttnn_visualizer/extensions.py index dfe23c85..b84518b1 100644 --- a/backend/ttnn_visualizer/extensions.py +++ b/backend/ttnn_visualizer/extensions.py @@ -1,18 +1,4 @@ -from flask_marshmallow import Marshmallow -from flask_sqlalchemy import SQLAlchemy from flask_static_digest import FlaskStaticDigest -class SQLiteAlchemy(SQLAlchemy): - def apply_driver_hacks(self, app, info, options): - options.update( - { - "isolation_level": "AUTOCOMMIT", - } - ) - super(SQLiteAlchemy, self).apply_driver_hacks(app, info, options) - - -db = SQLiteAlchemy() flask_static_digest = FlaskStaticDigest() -ma = Marshmallow() diff --git a/backend/ttnn_visualizer/models.py b/backend/ttnn_visualizer/models.py index 1588a5c3..9f8f593b 100644 --- a/backend/ttnn_visualizer/models.py +++ b/backend/ttnn_visualizer/models.py @@ -1,181 +1,152 @@ +import dataclasses +import enum import json - -from sqlalchemy import ( - Column, - PrimaryKeyConstraint, - Table, - Integer, - String, - JSON, - types, - Text, - Float, - TypeDecorator, -) -from sqlalchemy.orm import relationship, Mapped, mapped_column - -from ttnn_visualizer.extensions import db - -operations = Table( - "operations", - db.metadata, - Column("operation_id", Integer, primary_key=True), - Column("name", String), - Column("duration", Float), -) - -# TODO Ask about PK for this table (UUID for instance) -operation_arguments = Table( - "operation_arguments", - db.metadata, - Column("operation_id", db.ForeignKey("operations.operation_id")), - Column("name", Text), - Column("value", Text), - PrimaryKeyConstraint("operation_id", "value", "name"), -) - -# TODO Ask about PK for this table (UUID for instance) -input_tensors = Table( - "input_tensors", - db.metadata, - Column("operation_id", db.ForeignKey("operations.operation_id")), - Column("input_index", Integer), - Column("tensor_id", db.ForeignKey("tensors.tensor_id")), - PrimaryKeyConstraint("operation_id", "input_index", "tensor_id"), -) - -tensors = Table( - "tensors", - db.metadata, - Column("tensor_id", Integer, primary_key=True), - Column("shape", Text), - Column("dtype", Text), - Column("layout", Text), - Column("memory_config", Text), - Column("device_id", Integer), - Column("address", Integer), - Column("buffer_type", Integer), -) - -output_tensors = Table( - "output_tensors", - db.metadata, - Column("operation_id", db.ForeignKey("operations.operation_id")), - Column("output_index", Integer), - Column("tensor_id", db.ForeignKey("tensors.tensor_id")), - PrimaryKeyConstraint("operation_id", "output_index", "tensor_id"), -) - -stack_traces = Table( - "stack_traces", - db.metadata, - Column("operation_id", db.ForeignKey("operations.operation_id")), - Column("stack_trace", Text), - PrimaryKeyConstraint("operation_id", "stack_trace"), -) - -buffers = Table( - "buffers", - db.metadata, - Column("operation_id", db.ForeignKey("operations.operation_id")), - Column("device_id", db.ForeignKey("devices.device_id")), - Column("address", Integer), - Column("max_size_per_bank", Integer), - Column("buffer_type", Integer), - PrimaryKeyConstraint("operation_id", "device_id", "address", "max_size_per_bank"), -) - -devices = Table( - "devices", - db.metadata, - Column("device_id", Integer, primary_key=True), - Column("num_y_cores", Integer), - Column("num_x_cores", Integer), - Column("num_y_compute_cores", Integer), - Column("num_x_compute_cores", Integer), - Column("worker_l1_size", Integer), - Column("l1_num_banks", Integer), - Column("l1_bank_size", Integer), - Column("address_at_first_l1_bank", Integer), - Column("address_at_first_l1_cb_buffer", Integer), - Column("num_banks_per_storage_core", Integer), - Column("num_compute_cores", Integer), - Column("num_storage_cores", Integer), - Column("total_l1_memory", Integer), - Column("total_l1_for_tensors", Integer), - Column("total_l1_for_interleaved_buffers", Integer), - Column("total_l1_for_sharded_buffers", Integer), - Column("cb_limit", Integer), -) - -device_operations = Table( - "captured_graph", - db.metadata, - Column("operation_id", db.ForeignKey("operations.operation_id")), - Column( - "captured_graph", - Text, - ), - PrimaryKeyConstraint("operation_id", "captured_graph"), -) - - -class Device(db.Model): - __table__ = devices - - -class Tensor(db.Model): - __table__ = tensors - input_tensors = relationship("InputTensor", back_populates="tensor", lazy="joined") - output_tensors = relationship( - "OutputTensor", back_populates="tensor", lazy="joined" - ) - - @property - def producers(self): - return [i.operation_id for i in self.output_tensors] - - @property - def consumers(self): - return [i.operation_id for i in self.input_tensors] - - -class Buffer(db.Model): - __table__ = buffers - device = relationship("Device") - - -class InputTensor(db.Model): - __table__ = input_tensors - tensor = db.relationship( - "Tensor", back_populates="input_tensors", innerjoin=True, lazy="joined" - ) - - -class StackTrace(db.Model): - __table__ = stack_traces - - -class OutputTensor(db.Model): - __table__ = output_tensors - tensor = db.relationship( - "Tensor", back_populates="output_tensors", innerjoin=True, lazy="joined" - ) - - -class Operation(db.Model): - __table__ = operations - arguments = db.relationship("OperationArgument", lazy="joined") - inputs = db.relationship("InputTensor", lazy="joined") - outputs = db.relationship("OutputTensor", lazy="joined") - stack_trace = db.relationship("StackTrace", lazy="joined") - buffers = db.relationship("Buffer") - device_operations = db.relationship("DeviceOperation", uselist=False, lazy="joined") - - -class OperationArgument(db.Model): - __table__ = operation_arguments - - -class DeviceOperation(db.Model): - __table__ = device_operations +from json import JSONDecodeError + + +class BufferType(enum.Enum): + DRAM = 0 + L1 = 1 + SYSTEM_MEMORY = 2 + L1_SMALL = 3 + TRACE = 4 + + +@dataclasses.dataclass +class Operation: + operation_id: int + name: str + duration: float + + +@dataclasses.dataclass +class Device: + device_id: int + num_y_cores: int + num_x_cores: int + num_y_compute_cores: int + num_x_compute_cores: int + worker_l1_size: int + l1_num_banks: int + l1_bank_size: int + address_at_first_l1_bank: int + address_at_first_l1_cb_buffer: int + num_banks_per_storage_core: int + num_compute_cores: int + num_storage_cores: int + total_l1_memory: int + total_l1_for_tensors: int + total_l1_for_interleaved_buffers: int + total_l1_for_sharded_buffers: int + cb_limit: int + + +@dataclasses.dataclass +class DeviceOperation: + operation_id: int + captured_graph: str + + def __post_init__(self): + try: + captured_graph = json.loads(self.captured_graph) + for graph in captured_graph: + id = graph.pop("counter") + graph.update({"id": id}) + + self.captured_graph = captured_graph + + except JSONDecodeError: + self.captured_graph = [] + + +@dataclasses.dataclass +class Buffer: + operation_id: int + device_id: int + address: int + max_size_per_bank: int + buffer_type: BufferType + + def __post_init__(self): + self.buffer_type = ( + BufferType(self.buffer_type).value if self.buffer_type is not None else None + ) + + +@dataclasses.dataclass +class BufferPage: + operation_id: int + device_id: int + address: int + core_y: int + core_x: int + bank_id: int + page_index: int + page_address: int + page_size: int + buffer_type: BufferType + + def __post_init__(self): + self.buffer_type = ( + BufferType(self.buffer_type).value if self.buffer_type is not None else None + ) + + +@dataclasses.dataclass +class ProducersConsumers: + tensor_id: int + producers: list[int] + consumers: list[int] + + +@dataclasses.dataclass +class Tensor: + tensor_id: int + shape: str + dtype: str + layout: str + memory_config: str + device_id: int + address: int + buffer_type: BufferType + + def __post_init__(self): + self.buffer_type = ( + BufferType(self.buffer_type).value if self.buffer_type is not None else None + ) + + +@dataclasses.dataclass +class InputTensor: + operation_id: int + input_index: int + tensor_id: int + + +@dataclasses.dataclass +class OutputTensor: + operation_id: int + output_index: int + tensor_id: int + + +@dataclasses.dataclass +class TensorComparisonRecord: + tensor_id: int + golden_tensor_id: int + matches: bool + desired_pcc: bool + actual_pcc: float + + +@dataclasses.dataclass +class OperationArgument: + operation_id: int + name: str + value: str + + +@dataclasses.dataclass +class StackTrace: + operation_id: int + stack_trace: str diff --git a/backend/ttnn_visualizer/queries.py b/backend/ttnn_visualizer/queries.py new file mode 100644 index 00000000..66b9b006 --- /dev/null +++ b/backend/ttnn_visualizer/queries.py @@ -0,0 +1,260 @@ +import dataclasses +import sqlite3 +from functools import wraps +from pathlib import Path +from timeit import default_timer +from typing import Callable + +from ttnn_visualizer.models import ( + Operation, + Device, + DeviceOperation, + Buffer, + BufferPage, + ProducersConsumers, + Tensor, + InputTensor, + OutputTensor, + OperationArgument, + StackTrace, +) + + +def query_operation_by_id(cursor, operation_id): + query = "SELECT * FROM operations WHERE operation_id = ?" + cursor.execute(query, (operation_id,)) + row = cursor.fetchone() + if row: + return Operation(*row) + + +def query_operations(cursor): + query = "SELECT * FROM operations" + cursor.execute(query) + for row in cursor.fetchall(): + operation = Operation(*row) + yield operation + + +def query_operation_arguments(cursor): + query = "SELECT * FROM operation_arguments" + cursor.execute(query) + for row in cursor.fetchall(): + operation_argument = OperationArgument(*row) + yield operation_argument + + +def query_operation_arguments_by_operation_id(cursor, operation_id): + query = "SELECT * FROM operation_arguments WHERE operation_id = ?" + cursor.execute(query, (operation_id,)) + for row in cursor.fetchall(): + operation_argument = OperationArgument(*row) + yield operation_argument + + +def query_stack_traces(cursor): + query = "SELECT * FROM stack_traces" + cursor.execute(query) + for row in cursor.fetchall(): + stack_trace = StackTrace(*row) + yield stack_trace + + +def query_stack_trace(cursor, operation_id): + query = "SELECT * FROM stack_traces WHERE operation_id = ?" + cursor.execute(query, (operation_id,)) + stack_trace = None + for row in cursor.fetchall(): + _, stack_trace = row + break + return stack_trace + + +def query_buffers(cursor, operation_id): + query = "SELECT * FROM buffers WHERE operation_id = ?" + cursor.execute(query, (operation_id,)) + for row in cursor.fetchall(): + yield Buffer(*row) + + +def query_buffer_pages(cursor, operation_id): + query = "SELECT * FROM buffer_pages WHERE operation_id = ?" + cursor.execute(query, (operation_id,)) + for row in cursor.fetchall(): + yield BufferPage(*row) + + +def query_tensor_by_id(cursor, tensor_id): + query = "SELECT * FROM tensors WHERE tensor_id = ?" + cursor.execute(query, (tensor_id,)) + tensor = None + for row in cursor.fetchall(): + tensor = Tensor(*row) + break + return tensor + + +def query_input_tensors(cursor): + query = "SELECT * FROM input_tensors" + cursor.execute(query) + for row in cursor.fetchall(): + yield InputTensor(*row) + + +def query_input_tensors_by_operation_id(cursor, operation_id): + query = "SELECT * FROM input_tensors WHERE operation_id = ?" + cursor.execute(query, (operation_id,)) + for row in cursor.fetchall(): + yield InputTensor(*row) + + +def query_output_tensors_by_operation_id(cursor, operation_id): + query = "SELECT * FROM output_tensors WHERE operation_id = ?" + cursor.execute(query, (operation_id,)) + for row in cursor.fetchall(): + yield OutputTensor(*row) + + +def query_output_tensors(cursor): + query = "SELECT * from output_tensors" + cursor.execute(query) + for row in cursor.fetchall(): + yield OutputTensor(*row) + + +def query_tensors_by_tensor_ids(cursor, tensor_ids): + query = "SELECT * FROM tensors WHERE tensor_id IN ({})".format( + ",".join("?" * len(tensor_ids)) + ) + cursor.execute(query, tensor_ids) + for row in cursor.fetchall(): + yield Tensor(*row) + + +def query_tensors(cursor): + query = "SELECT * FROM tensors" + cursor.execute(query) + for row in cursor.fetchall(): + yield Tensor(*row) + + +def query_output_tensors_by_id(cursor, operation_id): + query = "SELECT * FROM output_tensors WHERE operation_id = ?" + cursor.execute(query, (operation_id,)) + for row in cursor.fetchall(): + yield OutputTensor(*row) + + +def query_output_tensor_by_tensor_id(cursor, tensor_id): + query = "SELECT * FROM output_tensors WHERE tensor_id = ?" + cursor.execute(query, (tensor_id,)) + output_tensor = None + for row in cursor.fetchall(): + output_tensor = OutputTensor(*row) + break + return output_tensor + + +def query_devices(cursor): + query = "SELECT * from devices" + cursor.execute(query) + for row in cursor.fetchall(): + device = Device(*row) + yield device + + +# Function to check if a table exists +def check_table_exists(cursor, table_name): + query = "SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?" + cursor.execute( + query, + (table_name,), + ) + result = cursor.fetchone() + return bool(result) + + +def query_device_operations(cursor): + if check_table_exists(cursor, "captured_graph"): + cursor.execute("SELECT * FROM captured_graph") + for row in cursor.fetchall(): + yield DeviceOperation(*row) + else: + yield [] + + +def query_next_buffer(cursor, operation_id, address): + query = """ + SELECT + buffers.operation_id AS buffers_operation_id, + buffers.device_id AS buffers_device_id, + buffers.address AS buffers_address, + buffers.max_size_per_bank AS buffers_max_size_per_bank, + buffers.buffer_type AS buffers_buffer_type + FROM + buffers + WHERE + buffers.address = ? + AND buffers.operation_id > ? + ORDER BY buffers.operation_id + """ + cursor.execute(query, (address, operation_id)) + row = cursor.fetchone() + return Buffer(*row) + + +def query_device_operations_by_operation_id(cursor, operation_id): + device_operation = None + query = "SELECT * FROM captured_graph WHERE operation_id = ?" + if check_table_exists(cursor, "captured_graph"): + cursor.execute(query, (operation_id,)) + result = cursor.fetchone() + if not result: + return None + operation_id, captured_graph = result + device_operation = DeviceOperation( + operation_id=operation_id, captured_graph=captured_graph + ) + + return device_operation + + +def query_consumer_operation_ids(cursor, tensor_id): + query = "SELECT * FROM input_tensors WHERE tensor_id = ?" + cursor.execute(query, (tensor_id,)) + for row in cursor.fetchall(): + operation_id, *_ = row + yield operation_id + + +def query_producers_consumers(cursor): + query = """ + SELECT + t.tensor_id, + GROUP_CONCAT(ot.operation_id, ', ') AS consumers, + GROUP_CONCAT(it.operation_id, ', ') AS producers + FROM + tensors t + LEFT JOIN + input_tensors it ON t.tensor_id = it.tensor_id + LEFT JOIN + output_tensors ot on t.tensor_id = ot.tensor_id + GROUP BY + t.tensor_id + + """ + cursor.execute(query) + for row in cursor.fetchall(): + producers = [] + consumers = [] + tensor_id, producers_data, consumers_data = row + if producers_data: + unique_producers = set(map(int, producers_data.split(","))) + producers = list(unique_producers) + producers.sort() + if consumers_data: + unique_consumers = set(map(int, consumers_data.split(","))) + consumers = list(unique_consumers) + consumers.sort() + producer_consumers = ProducersConsumers(tensor_id, producers, consumers) + yield producer_consumers diff --git a/backend/ttnn_visualizer/requirements.txt b/backend/ttnn_visualizer/requirements.txt index d979be64..5db05a89 100644 --- a/backend/ttnn_visualizer/requirements.txt +++ b/backend/ttnn_visualizer/requirements.txt @@ -2,14 +2,10 @@ gunicorn~=22.0.0 uvicorn==0.30.1 paramiko~=3.4.0 -marshmallow==3.21.3 -flask_marshmallow==1.2.1 flask_cors==4.0.1 -flask_sqlalchemy==3.1.1 pydantic==2.7.3 pydantic_core==2.18.4 flask_static_digest==0.4.1 -marshmallow-sqlalchemy==1.0.0 setuptools==65.5.0 python-dotenv==1.0.1 wheel diff --git a/backend/ttnn_visualizer/schemas.py b/backend/ttnn_visualizer/schemas.py deleted file mode 100644 index 63092af4..00000000 --- a/backend/ttnn_visualizer/schemas.py +++ /dev/null @@ -1,145 +0,0 @@ -import json - -from marshmallow import fields, validates - -from ttnn_visualizer.extensions import ma -from ttnn_visualizer.models import ( - Tensor, - OperationArgument, - Operation, - InputTensor, - StackTrace, - OutputTensor, - Buffer, - DeviceOperation, -) - - -# Database Schemas - - -class TensorSchema(ma.SQLAlchemyAutoSchema): - class Meta: - model = Tensor - - shape = ma.auto_field() - address = ma.auto_field() - id = ma.Function(lambda obj: obj.tensor_id, dump_only=True) - layout = ma.auto_field() - memory_config = ma.auto_field() - buffer_type = ma.auto_field() - producers = ma.Function(lambda obj: obj.producers, dump_only=True) - consumers = ma.Function(lambda obj: obj.consumers, dump_only=True) - - -class StackTraceSchema(ma.SQLAlchemyAutoSchema): - class Meta: - model = StackTrace - - stack_trace = ma.Function(lambda obj: obj.stack_trace or "") - - -class InputOutputSchema(object): - id = ma.Function(lambda obj: obj.tensor_id) - operation_id = ma.auto_field() - shape = ma.Function(lambda obj: obj.tensor.shape) - address = ma.Function(lambda obj: obj.tensor.address) - layout = ma.Function(lambda obj: obj.tensor.layout) - memory_config = ma.Function(lambda obj: obj.tensor.memory_config) - device_id = ma.Function(lambda obj: obj.tensor.device_id) - buffer_type = ma.Function(lambda obj: obj.tensor.buffer_type) - dtype = ma.Function(lambda obj: obj.tensor.dtype) - producers = ma.Function(lambda obj: obj.tensor.producers) - consumers = ma.Function(lambda obj: obj.tensor.consumers) - - -class OutputTensorSchema(ma.SQLAlchemyAutoSchema, InputOutputSchema): - class Meta: - model = OutputTensor - - output_index = ma.auto_field() - operation_id = ma.auto_field() - tensor_id = ma.auto_field() - - -class InputTensorSchema(ma.SQLAlchemyAutoSchema, InputOutputSchema): - class Meta: - model = InputTensor - - input_index = ma.auto_field() - operation_id = ma.auto_field() - tensor_id = ma.auto_field() - - -class OperationArgumentsSchema(ma.SQLAlchemySchema): - class Meta: - model = OperationArgument - - operation_id = ma.auto_field() - name = ma.auto_field() - value = ma.auto_field() - - -class CapturedGraph(fields.Field): - def _serialize(self, value, attr, obj, **kwargs): - if value is None: - return [] - values = json.loads(value) - for value in values: - id = value.pop("counter") - value.update({"id": id}) - return values - - -class DeviceOperationSchema(ma.SQLAlchemyAutoSchema): - class Meta: - model = DeviceOperation - - operation_id = ma.auto_field() - captured_graph = CapturedGraph() - - -class BufferSchema(ma.SQLAlchemyAutoSchema): - class Meta: - model = Buffer - - operation_id = ma.auto_field() - address = ma.auto_field() - max_size_per_bank = ma.auto_field() - buffer_type = ma.auto_field() - device_id = ma.Function(lambda obj: obj.device.device_id) - - -class OperationSchema(ma.SQLAlchemySchema): - class Meta: - model = Operation - - operation_id = ma.auto_field() - id = ma.Function(lambda obj: obj.operation_id) - name = ma.auto_field() - duration = ma.auto_field() - buffers = ma.List(ma.Nested(BufferSchema())) - outputs = ma.List(ma.Nested(OutputTensorSchema())) - inputs = ma.List(ma.Nested(InputTensorSchema())) - arguments = ma.List(ma.Nested(OperationArgumentsSchema())) - stack_trace = ma.Method("get_stack_trace") - device_operations = fields.Pluck(DeviceOperationSchema(), "captured_graph") - - def get_stack_trace(self, obj): - if obj.stack_trace and len(obj.stack_trace): - return next(x for x in obj.stack_trace).stack_trace - return "" - - -# Filesystem Schemas -class RemoteConnectionSchema(ma.Schema): - name = fields.Str() - host = fields.Str(required=True, error_messages={"required": "Host is required"}) - port = fields.Int(required=True, error_messages={"required": "Port is required"}) - path = fields.Str(required=True, error_messages={"required": "Path is required"}) - - @validates("path") - def validate_path(self, path): - # TODO Validate valid path format - # TODO Could validate on the front end as well - pass diff --git a/backend/ttnn_visualizer/serializers.py b/backend/ttnn_visualizer/serializers.py new file mode 100644 index 00000000..8fa7e22f --- /dev/null +++ b/backend/ttnn_visualizer/serializers.py @@ -0,0 +1,162 @@ +import dataclasses +from collections import defaultdict + + +def serialize_operations( + inputs, + operation_arguments, + operations, + outputs, + stack_traces, + tensors, + devices, + producers_consumers, + device_operations, +): + tensors_dict = dict() + for t in tensors: + tensors_dict.update({t.tensor_id: t}) + + device_operations_dict = dict() + for device_operation in device_operations: + device_operations_dict.update( + {device_operation.operation_id: device_operation.captured_graph} + ) + + stack_traces_dict = defaultdict(str) + for stack_trace in stack_traces: + stack_traces_dict.update({stack_trace.operation_id: stack_trace.stack_trace}) + + # Join Arguments + arguments_dict = defaultdict(list) + for argument in operation_arguments: + arguments_dict[argument.operation_id].append(argument) + + inputs_dict, outputs_dict = serialize_inputs_outputs( + inputs, outputs, producers_consumers, tensors_dict + ) + + # Serialize Final Results + results = [] + for operation in operations: + inputs = inputs_dict[operation.operation_id] + outputs = outputs_dict[operation.operation_id] + arguments = [ + dataclasses.asdict(a) for a in arguments_dict[operation.operation_id] + ] + operation_data = dataclasses.asdict(operation) + operation_data.update({"id": operation.operation_id}) + operation_device_operations = device_operations_dict.get( + operation.operation_id, [] + ) + + results.append( + dict( + **operation_data, + stack_trace=stack_traces_dict.get(operation.operation_id), + device_operations=operation_device_operations, + arguments=arguments, + inputs=inputs, + outputs=outputs, + ) + ) + return results + + +def serialize_inputs_outputs(inputs, outputs, producers_consumers, tensors_dict): + + producers_consumers_dict = dict() + for pc in producers_consumers: + producers_consumers_dict.update({pc.tensor_id: pc}) + + def attach_producers_consumers(producers_consumers_dict, values): + values_dict = defaultdict(list) + for value in values: + value_dict = dataclasses.asdict(value) + tensor = tensors_dict.get(value.tensor_id) + tensor_dict = dataclasses.asdict(tensor) + producers_consumers = producers_consumers_dict.get(value.tensor_id) + value_dict.update( + { + "id": tensor_dict.pop("tensor_id"), + "consumers": producers_consumers.consumers, + "producers": producers_consumers.producers, + } + ) + values_dict[value.operation_id].append(dict(**value_dict, **tensor_dict)) + return values_dict + + inputs_dict = attach_producers_consumers(producers_consumers_dict, inputs) + outputs_dict = attach_producers_consumers(producers_consumers_dict, outputs) + return inputs_dict, outputs_dict + + +def serialize_operation( + buffers, + inputs, + operation, + operation_arguments, + outputs, + stack_trace, + tensors, + devices, + producers_consumers, + device_operations, +): + + tensors_dict = dict() + for t in tensors: + tensors_dict.update({t.tensor_id: t}) + + producers_consumers_dict = dict() + for pc in producers_consumers: + producers_consumers_dict.update({pc.tensor_id: pc}) + + inputs_dict, outputs_dict = serialize_inputs_outputs( + inputs, outputs, producers_consumers, tensors_dict + ) + + # Serialize Buffers + buffer_list = [] + for buffer in buffers: + buffer_data = dataclasses.asdict(buffer) + buffer_list.append(buffer_data) + + l1_sizes = [d.worker_l1_size for d in devices] + arguments_data = [dataclasses.asdict(argument) for argument in operation_arguments] + operation_data = operation.__dict__.copy() + operation_data.update({"id": operation.operation_id}) + + inputs_data = inputs_dict.get(operation.operation_id) + outputs_data = outputs_dict.get(operation.operation_id) + + return dict( + **operation_data, + l1_sizes=l1_sizes, + device_operations=device_operations.captured_graph if device_operations else [], + stack_trace=stack_trace or "", + buffers=buffer_list, + arguments=arguments_data, + inputs=inputs_data or [], + outputs=outputs_data or [], + ) + + +def serialize_tensors(tensors, producers_consumers): + producers_consumers_dict = dict() + for pc in producers_consumers: + producers_consumers_dict.update({pc.tensor_id: pc}) + results = [] + for tensor in tensors: + tensor_data = dataclasses.asdict(tensor) + tensor_id = tensor_data.pop("tensor_id") + tensor_data.update( + { + "id": tensor_id, + "consumers": producers_consumers_dict[tensor_id].consumers, + "producers": producers_consumers_dict[tensor_id].producers, + } + ) + + results.append(tensor_data) + return results diff --git a/backend/ttnn_visualizer/settings.py b/backend/ttnn_visualizer/settings.py index 371a7ca3..d28fd126 100644 --- a/backend/ttnn_visualizer/settings.py +++ b/backend/ttnn_visualizer/settings.py @@ -7,39 +7,28 @@ class Config(object): TEST_CONFIG_FILE = "config.json" REPORT_DATA_DIRECTORY = Path(__file__).parent.absolute().joinpath("data") - ACTIVE_DATA_DIRECTORY = Path(REPORT_DATA_DIRECTORY).joinpath("active") - ACTIVE_DB_PATH = Path(ACTIVE_DATA_DIRECTORY, "db.sqlite") SEND_FILE_MAX_AGE_DEFAULT = 0 - SQLALCHEMY_TRACK_MODIFICATIONS = False MIGRATE_ON_COPY = True - SQLALCHEMY_ECHO = False + SQLITE_DB_PATH = "db.sqlite" SECRET_KEY = os.getenv("SECRET_KEY", "90909") DEBUG = bool(str_to_bool(os.getenv("FLASK_DEBUG", "false"))) TESTING = False - - # SQLAlchemy. - DATABASE_OPTIONS = "check_same_thread=False" APPLICATION_DIR = os.path.abspath(os.path.join(__file__, "..", os.pardir)) + ACTIVE_DATA_DIRECTORY = Path(REPORT_DATA_DIRECTORY).joinpath("active") + ACTIVE_DB_PATH = Path(ACTIVE_DATA_DIRECTORY, "db.sqlite") DATABASE_FILE = ACTIVE_DB_PATH - SQLALCHEMY_DATABASE_URI = os.getenv( - "DATABASE_URL", "sqlite:///" + f"{DATABASE_FILE}?{DATABASE_OPTIONS}" - ) - class DevelopmentConfig(Config): pass class TestingConfig(Config): - SQLALCHEMY_TRACK_MODIFICATIONS = False DEBUG = bool(str_to_bool(os.getenv("FLASK_DEBUG", "True"))) TESTING = True class ProductionConfig(Config): - SQLALCHEMY_TRACK_MODIFICATIONS = True - SQLALCHEMY_ECHO = False DEBUG = False TESTING = False diff --git a/backend/ttnn_visualizer/utils.py b/backend/ttnn_visualizer/utils.py index 320d0ed1..1c383184 100644 --- a/backend/ttnn_visualizer/utils.py +++ b/backend/ttnn_visualizer/utils.py @@ -1,6 +1,7 @@ import logging from functools import wraps from timeit import default_timer +from typing import Callable logger = logging.getLogger(__name__) @@ -9,13 +10,14 @@ def str_to_bool(string_value): return string_value.lower() in ("yes", "true", "t", "1") -def timer(f): +def timer(f: Callable): @wraps(f) def wrapper(*args, **kwargs): + start_time = default_timer() response = f(*args, **kwargs) total_elapsed_time = default_timer() - start_time - logger.info(f"Elapsed time: {total_elapsed_time}") + logger.info(f"{f.__name__}: Elapsed time: {total_elapsed_time:0.4f} seconds") return response return wrapper diff --git a/backend/ttnn_visualizer/views.py b/backend/ttnn_visualizer/views.py index 290155c2..29d91f79 100644 --- a/backend/ttnn_visualizer/views.py +++ b/backend/ttnn_visualizer/views.py @@ -1,12 +1,14 @@ +import dataclasses import json import logging import shutil +import sqlite3 from http import HTTPStatus from pathlib import Path + from ttnn_visualizer.database import create_update_database from flask import Blueprint, Response, current_app, request -from ttnn_visualizer.models import Device, Operation, Tensor, Buffer from ttnn_visualizer.remotes import ( RemoteConnection, RemoteFolder, @@ -17,12 +19,13 @@ read_remote_file, sync_test_folders, ) -from ttnn_visualizer.schemas import ( - OperationSchema, - TensorSchema, - BufferSchema, +from ttnn_visualizer.serializers import ( + serialize_operations, + serialize_tensors, + serialize_operation, ) from ttnn_visualizer.utils import timer +from ttnn_visualizer import queries logger = logging.getLogger(__name__) @@ -37,28 +40,104 @@ def health_check(): @api.route("/operations", methods=["GET"]) @timer def operation_list(): - operations = Operation.query.all() - return OperationSchema( - many=True, - exclude=[ - "buffers", - "operation_id", - ], - ).dump(operations) + db_path = get_db_path_from_request(request) + + if not Path(db_path).exists(): + return Response(status=HTTPStatus.BAD_REQUEST) + + with sqlite3.connect(db_path) as conn: + cursor = conn.cursor() + operations = list(queries.query_operations(cursor)) + operations.sort(key=lambda o: o.operation_id) + operation_arguments = list(queries.query_operation_arguments(cursor)) + device_operations = list(queries.query_device_operations(cursor)) + stack_traces = list(queries.query_stack_traces(cursor)) + outputs = list(queries.query_output_tensors(cursor)) + tensors = list(queries.query_tensors(cursor)) + inputs = list(queries.query_input_tensors(cursor)) + devices = list(queries.query_devices(cursor)) + producers_consumers = list(queries.query_producers_consumers(cursor)) + + return serialize_operations( + inputs, + operation_arguments, + operations, + outputs, + stack_traces, + tensors, + devices, + producers_consumers, + device_operations, + ) + + +def get_db_path_from_request(request: None): + """ + Parses the request to determine the path of the target database + TODO At the moment we simply use one database 'active' + In future will target databases on a per-request basis + :param request: + :return: + """ + report_path = current_app.config["ACTIVE_DATA_DIRECTORY"] + db_path = current_app.config["SQLITE_DB_PATH"] + return report_path / db_path @api.route("/operations/", methods=["GET"]) +@timer def operation_detail(operation_id): - operation = Operation.query.get(operation_id) - if not operation: - return Response(status=HTTPStatus.NOT_FOUND) - devices = Device.query.order_by(Device.device_id.asc()).all() - l1_sizes = [d.worker_l1_size for d in devices] - - return dict( - **OperationSchema().dump(operation), - l1_sizes=l1_sizes, - ) + db_path = get_db_path_from_request(request) + + if not Path(db_path).exists(): + return Response(status=HTTPStatus.BAD_REQUEST) + + with sqlite3.connect(db_path) as conn: + cursor = conn.cursor() + operation = queries.query_operation_by_id(cursor, operation_id) + + if not operation: + return Response(status=HTTPStatus.NOT_FOUND) + + buffers = queries.query_buffers(cursor, operation_id) + operation_arguments = queries.query_operation_arguments_by_operation_id( + cursor, operation_id + ) + stack_trace = queries.query_stack_trace(cursor, operation_id) + + inputs = list(queries.query_input_tensors_by_operation_id(cursor, operation_id)) + outputs = list( + queries.query_output_tensors_by_operation_id(cursor, operation_id) + ) + input_tensor_ids = [i.tensor_id for i in inputs] + output_tensor_ids = [o.tensor_id for o in outputs] + tensor_ids = input_tensor_ids + output_tensor_ids + tensors = list(queries.query_tensors_by_tensor_ids(cursor, tensor_ids)) + device_operations = queries.query_device_operations_by_operation_id( + cursor, operation_id + ) + + producers_consumers = list( + filter( + lambda pc: pc.tensor_id in tensor_ids, + queries.query_producers_consumers(cursor), + ) + ) + + devices = list(queries.query_devices(cursor)) + + return serialize_operation( + buffers, + inputs, + operation, + operation_arguments, + outputs, + stack_trace, + tensors, + devices, + producers_consumers, + device_operations, + ) @api.route( @@ -67,7 +146,7 @@ def operation_detail(operation_id): "GET", ], ) -def get_operation_history(): +def operation_history(): operation_history_filename = "operation_history.json" operation_history_file = Path( current_app.config["ACTIVE_DATA_DIRECTORY"], operation_history_filename @@ -89,39 +168,57 @@ def get_config(): @api.route("/tensors", methods=["GET"]) -def get_tensors(): - tensors = Tensor.query.all() - return TensorSchema(exclude=["tensor_id"]).dump(tensors, many=True) +@timer +def tensors_list(): + db_path = get_db_path_from_request(request) + + if not Path(db_path).exists(): + return Response(status=HTTPStatus.BAD_REQUEST) + + with sqlite3.connect(db_path) as conn: + cursor = conn.cursor() + tensors = list(queries.query_tensors(cursor)) + producers_consumers = list(queries.query_producers_consumers(cursor)) + return serialize_tensors(tensors, producers_consumers) @api.route("/buffer", methods=["GET"]) -def get_next_buffer(): +@timer +def buffer_detail(): address = request.args.get("address") operation_id = request.args.get("operation_id") if not address or not operation_id: return Response(status=HTTPStatus.BAD_REQUEST) - buffer = ( - Buffer.query.filter( - Buffer.address == address, Buffer.operation_id > operation_id - ) - .order_by(Buffer.operation_id.asc()) - .first() - ) + db_path = get_db_path_from_request(request) - if not buffer: - return Response(status=HTTPStatus.NOT_FOUND) + if not Path(db_path).exists(): + return Response(status=HTTPStatus.BAD_REQUEST) - return BufferSchema().dump(buffer) + with sqlite3.connect(db_path) as conn: + cursor = conn.cursor() + buffer = queries.query_next_buffer(cursor, operation_id, address) + if not buffer: + return Response(status=HTTPStatus.NOT_FOUND) + return dataclasses.asdict(buffer) @api.route("/tensors/", methods=["GET"]) -def get_tensor(tensor_id): - tensor = Tensor.query.get(tensor_id) - if not tensor: - return Response(status=HTTPStatus.NOT_FOUND) - return TensorSchema().dump(tensor) +@timer +def tensor_detail(tensor_id): + db_path = get_db_path_from_request(request) + + if not Path(db_path).exists(): + return Response(status=HTTPStatus.BAD_REQUEST) + + with sqlite3.connect(db_path) as conn: + cursor = conn.cursor() + tensor = queries.query_tensor_by_id(cursor, tensor_id) + if not tensor: + return Response(status=HTTPStatus.NOT_FOUND) + + return dataclasses.asdict(tensor) @api.route( diff --git a/package-lock.json b/package-lock.json index d5d76e9c..54550a09 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ttnn-visualzer", - "version": "0.2.0", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ttnn-visualzer", - "version": "0.2.0", + "version": "0.3.0", "dependencies": { "@blueprintjs/core": "^5.10.3", "@blueprintjs/select": "^5.1.5", @@ -39,9 +39,10 @@ "eslint-config-airbnb-base": "^15.0.0", "eslint-config-erb": "^4.0.6", "eslint-config-prettier": "^9.0.0", + "eslint-import-resolver-alias": "^1.1.2", "eslint-import-resolver-typescript": "^3.5.5", "eslint-plugin-compat": "^4.1.4", - "eslint-plugin-import": "^2.27.5", + "eslint-plugin-import": "^2.30.0", "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-promise": "^6.1.1", @@ -57,7 +58,7 @@ "stylelint-config-standard-scss": "^13.1.0", "stylelint-prettier": "^5.0.0", "svgo": "^1.3.2", - "typescript": "^5.2.2", + "typescript": "5.2", "vite": "^5.2.0" } }, @@ -693,10 +694,11 @@ } }, "node_modules/@blueprintjs/node-build-scripts": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@blueprintjs/node-build-scripts/-/node-build-scripts-9.2.1.tgz", - "integrity": "sha512-FbMuUs7r3f0Gkq/reqmew8dVWar6SeA5kOHiApdDeKIIMQ8Ab7/+6eUkAdeQGFqe1GXmsggJDWmldWwXPcb+eA==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@blueprintjs/node-build-scripts/-/node-build-scripts-9.2.2.tgz", + "integrity": "sha512-7E9DdS5e+VHibqXcuDCEyBba1yEYi7qCQ9Ma+TPJ372n/A+A0MPGFxB/fkKEXAuGMN09ZEQVWqAkuwvg9H89Dw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "autoprefixer": "^10.4.17", "chokidar": "^3.5.3", @@ -1117,13 +1119,14 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", - "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "aix" @@ -1133,13 +1136,14 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", - "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -1149,13 +1153,14 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", - "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -1165,13 +1170,14 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", - "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -1181,13 +1187,14 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", - "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -1197,13 +1204,14 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", - "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -1213,13 +1221,14 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", - "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -1229,13 +1238,14 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", - "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -1245,13 +1255,14 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", - "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -1261,13 +1272,14 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", - "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -1277,13 +1289,14 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", - "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -1293,13 +1306,14 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", - "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", "cpu": [ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -1309,13 +1323,14 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", - "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -1325,13 +1340,14 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", - "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -1341,13 +1357,14 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", - "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -1357,13 +1374,14 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", - "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -1373,13 +1391,14 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", - "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -1389,13 +1408,14 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", - "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -1405,13 +1425,14 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", - "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -1421,13 +1442,14 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", - "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "sunos" @@ -1437,13 +1459,14 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", - "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -1453,13 +1476,14 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", - "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -1469,13 +1493,14 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", - "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -2791,213 +2816,236 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", - "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.5.tgz", + "integrity": "sha512-SU5cvamg0Eyu/F+kLeMXS7GoahL+OoizlclVFX3l5Ql6yNlywJJ0OuqTzUx0v+aHhPHEB/56CT06GQrRrGNYww==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", - "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.5.tgz", + "integrity": "sha512-S4pit5BP6E5R5C8S6tgU/drvgjtYW76FBuG6+ibG3tMvlD1h9LHVF9KmlmaUBQ8Obou7hEyS+0w+IR/VtxwNMQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", - "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.5.tgz", + "integrity": "sha512-250ZGg4ipTL0TGvLlfACkIxS9+KLtIbn7BCZjsZj88zSg2Lvu3Xdw6dhAhfe/FjjXPVNCtcSp+WZjVsD3a/Zlw==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", - "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.5.tgz", + "integrity": "sha512-D8brJEFg5D+QxFcW6jYANu+Rr9SlKtTenmsX5hOSzNYVrK5oLAEMTUgKWYJP+wdKyCdeSwnapLsn+OVRFycuQg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", - "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.5.tgz", + "integrity": "sha512-PNqXYmdNFyWNg0ma5LdY8wP+eQfdvyaBAojAXgO7/gs0Q/6TQJVXAXe8gwW9URjbS0YAammur0fynYGiWsKlXw==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", - "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.5.tgz", + "integrity": "sha512-kSSCZOKz3HqlrEuwKd9TYv7vxPYD77vHSUvM2y0YaTGnFc8AdI5TTQRrM1yIp3tXCKrSL9A7JLoILjtad5t8pQ==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", - "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.5.tgz", + "integrity": "sha512-oTXQeJHRbOnwRnRffb6bmqmUugz0glXaPyspp4gbQOPVApdpRrY/j7KP3lr7M8kTfQTyrBUzFjj5EuHAhqH4/w==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", - "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.5.tgz", + "integrity": "sha512-qnOTIIs6tIGFKCHdhYitgC2XQ2X25InIbZFor5wh+mALH84qnFHvc+vmWUpyX97B0hNvwNUL4B+MB8vJvH65Fw==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", - "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.5.tgz", + "integrity": "sha512-TMYu+DUdNlgBXING13rHSfUc3Ky5nLPbWs4bFnT+R6Vu3OvXkTkixvvBKk8uO4MT5Ab6lC3U7x8S8El2q5o56w==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", - "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.5.tgz", + "integrity": "sha512-PTQq1Kz22ZRvuhr3uURH+U/Q/a0pbxJoICGSprNLAoBEkyD3Sh9qP5I0Asn0y0wejXQBbsVMRZRxlbGFD9OK4A==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", - "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.5.tgz", + "integrity": "sha512-bR5nCojtpuMss6TDEmf/jnBnzlo+6n1UhgwqUvRoe4VIotC7FG1IKkyJbwsT7JDsF2jxR+NTnuOwiGv0hLyDoQ==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", - "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.5.tgz", + "integrity": "sha512-N0jPPhHjGShcB9/XXZQWuWBKZQnC1F36Ce3sDqWpujsGjDz/CQtOL9LgTrJ+rJC8MJeesMWrMWVLKKNR/tMOCA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", - "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.5.tgz", + "integrity": "sha512-uBa2e28ohzNNwjr6Uxm4XyaA1M/8aTgfF2T7UIlElLaeXkgpmIJ2EitVNQxjO9xLLLy60YqAgKn/AqSpCUkE9g==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", - "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.5.tgz", + "integrity": "sha512-RXT8S1HP8AFN/Kr3tg4fuYrNxZ/pZf1HemC5Tsddc6HzgGnJm0+Lh5rAHJkDuW3StI0ynNXukidROMXYl6ew8w==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", - "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.5.tgz", + "integrity": "sha512-ElTYOh50InL8kzyUD6XsnPit7jYCKrphmddKAe1/Ytt74apOxDq5YEcbsiKs0fR3vff3jEneMM+3I7jbqaMyBg==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", - "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.5.tgz", + "integrity": "sha512-+lvL/4mQxSV8MukpkKyyvfwhH266COcWlXE/1qxwN08ajovta3459zrjLghYMgDerlzNwLAcFpvU+WWE5y6nAQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -3171,10 +3219,11 @@ } }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" }, "node_modules/@types/graceful-fs": { "version": "4.1.9", @@ -3994,9 +4043,10 @@ } }, "node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -5899,11 +5949,12 @@ } }, "node_modules/esbuild": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", - "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -5911,29 +5962,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.2", - "@esbuild/android-arm": "0.20.2", - "@esbuild/android-arm64": "0.20.2", - "@esbuild/android-x64": "0.20.2", - "@esbuild/darwin-arm64": "0.20.2", - "@esbuild/darwin-x64": "0.20.2", - "@esbuild/freebsd-arm64": "0.20.2", - "@esbuild/freebsd-x64": "0.20.2", - "@esbuild/linux-arm": "0.20.2", - "@esbuild/linux-arm64": "0.20.2", - "@esbuild/linux-ia32": "0.20.2", - "@esbuild/linux-loong64": "0.20.2", - "@esbuild/linux-mips64el": "0.20.2", - "@esbuild/linux-ppc64": "0.20.2", - "@esbuild/linux-riscv64": "0.20.2", - "@esbuild/linux-s390x": "0.20.2", - "@esbuild/linux-x64": "0.20.2", - "@esbuild/netbsd-x64": "0.20.2", - "@esbuild/openbsd-x64": "0.20.2", - "@esbuild/sunos-x64": "0.20.2", - "@esbuild/win32-arm64": "0.20.2", - "@esbuild/win32-ia32": "0.20.2", - "@esbuild/win32-x64": "0.20.2" + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, "node_modules/escalade": { @@ -6507,6 +6558,19 @@ "node": ">=8" } }, + "node_modules/eslint-import-resolver-alias": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-alias/-/eslint-import-resolver-alias-1.1.2.tgz", + "integrity": "sha512-WdviM1Eu834zsfjHtcGHtGfcu+F30Od3V7I9Fi57uhBEwPkjDcii7/yW8jAT+gOhn4P/vOxxNAXbFAKsrrc15w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + }, + "peerDependencies": { + "eslint-plugin-import": ">=1.4.0" + } + }, "node_modules/eslint-import-resolver-node": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", @@ -6570,10 +6634,11 @@ } }, "node_modules/eslint-module-utils": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", - "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^3.2.7" }, @@ -6617,26 +6682,28 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", - "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.30.0.tgz", + "integrity": "sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==", "dev": true, + "license": "MIT", "dependencies": { - "array-includes": "^3.1.7", - "array.prototype.findlastindex": "^1.2.3", + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", "array.prototype.flat": "^1.3.2", "array.prototype.flatmap": "^1.3.2", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.8.0", - "hasown": "^2.0.0", - "is-core-module": "^2.13.1", + "eslint-module-utils": "^2.9.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", - "object.fromentries": "^2.0.7", - "object.groupby": "^1.0.1", - "object.values": "^1.1.7", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", "semver": "^6.3.1", "tsconfig-paths": "^3.15.0" }, @@ -8741,11 +8808,15 @@ } }, "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "license": "MIT", "dependencies": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -11360,10 +11431,11 @@ } }, "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -12115,10 +12187,11 @@ "integrity": "sha512-ESj2+eBxhGrcA1azgHs7lARG5+5iLakc/6nlfbpjcLl00HuuUOIuORhYXN4D1HfvMSKuVtFQjAlnwi1JHEeDIw==" }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", - "dev": true + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "dev": true, + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -12296,9 +12369,9 @@ } }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "dev": true, "funding": [ { @@ -12314,10 +12387,11 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -13422,12 +13496,13 @@ } }, "node_modules/rollup": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", - "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.5.tgz", + "integrity": "sha512-WoinX7GeQOFMGznEcWA1WrTQCd/tpEbMkc3nuMs9BT0CPjMdSjPMTVClwWd4pgSQwJdP65SK9mTCNvItlr5o7w==", "dev": true, + "license": "MIT", "dependencies": { - "@types/estree": "1.0.5" + "@types/estree": "1.0.6" }, "bin": { "rollup": "dist/bin/rollup" @@ -13437,22 +13512,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.18.0", - "@rollup/rollup-android-arm64": "4.18.0", - "@rollup/rollup-darwin-arm64": "4.18.0", - "@rollup/rollup-darwin-x64": "4.18.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", - "@rollup/rollup-linux-arm-musleabihf": "4.18.0", - "@rollup/rollup-linux-arm64-gnu": "4.18.0", - "@rollup/rollup-linux-arm64-musl": "4.18.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", - "@rollup/rollup-linux-riscv64-gnu": "4.18.0", - "@rollup/rollup-linux-s390x-gnu": "4.18.0", - "@rollup/rollup-linux-x64-gnu": "4.18.0", - "@rollup/rollup-linux-x64-musl": "4.18.0", - "@rollup/rollup-win32-arm64-msvc": "4.18.0", - "@rollup/rollup-win32-ia32-msvc": "4.18.0", - "@rollup/rollup-win32-x64-msvc": "4.18.0", + "@rollup/rollup-android-arm-eabi": "4.22.5", + "@rollup/rollup-android-arm64": "4.22.5", + "@rollup/rollup-darwin-arm64": "4.22.5", + "@rollup/rollup-darwin-x64": "4.22.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.5", + "@rollup/rollup-linux-arm-musleabihf": "4.22.5", + "@rollup/rollup-linux-arm64-gnu": "4.22.5", + "@rollup/rollup-linux-arm64-musl": "4.22.5", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.5", + "@rollup/rollup-linux-riscv64-gnu": "4.22.5", + "@rollup/rollup-linux-s390x-gnu": "4.22.5", + "@rollup/rollup-linux-x64-gnu": "4.22.5", + "@rollup/rollup-linux-x64-musl": "4.22.5", + "@rollup/rollup-win32-arm64-msvc": "4.22.5", + "@rollup/rollup-win32-ia32-msvc": "4.22.5", + "@rollup/rollup-win32-x64-msvc": "4.22.5", "fsevents": "~2.3.2" } }, @@ -13772,10 +13847,11 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -15100,10 +15176,11 @@ } }, "node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -15289,14 +15366,15 @@ } }, "node_modules/vite": { - "version": "5.2.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.12.tgz", - "integrity": "sha512-/gC8GxzxMK5ntBwb48pR32GGhENnjtY30G4A0jemunsBkiEZFw60s8InGpN8gkhHEkjnRK1aSAxeQgwvFhUHAA==", + "version": "5.4.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", + "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==", "dev": true, + "license": "MIT", "dependencies": { - "esbuild": "^0.20.1", - "postcss": "^8.4.38", - "rollup": "^4.13.0" + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { "vite": "bin/vite.js" @@ -15315,6 +15393,7 @@ "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -15332,6 +15411,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, diff --git a/package.json b/package.json index 7cd666fb..f3e01743 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ttnn-visualzer", "private": true, - "version": "0.3.0", + "version": "0.3.1", "type": "module", "scripts": { "dev": "vite", @@ -49,9 +49,10 @@ "eslint-config-airbnb-base": "^15.0.0", "eslint-config-erb": "^4.0.6", "eslint-config-prettier": "^9.0.0", + "eslint-import-resolver-alias": "^1.1.2", "eslint-import-resolver-typescript": "^3.5.5", "eslint-plugin-compat": "^4.1.4", - "eslint-plugin-import": "^2.27.5", + "eslint-plugin-import": "^2.30.0", "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-promise": "^6.1.1", diff --git a/pyproject.toml b/pyproject.toml index 6aaf8d09..dee3a850 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "ttnn_visualizer" authors = [] -version = "0.3.0" +version = "0.3.1" description = "TT Visualizer" readme = "README.md" requires-python = ">=3.12" @@ -13,11 +13,9 @@ dependencies = [ "marshmallow==3.21.3", "flask_marshmallow==1.2.1", "flask_cors==4.0.1", - "flask_sqlalchemy==3.1.1", "pydantic==2.7.3", "pydantic_core==2.18.4", "flask_static_digest==0.4.1", - "marshmallow-sqlalchemy==1.0.0", "setuptools==65.5.0", "python-dotenv==1.0.1", ] diff --git a/src/components/BufferDetails.tsx b/src/components/BufferDetails.tsx index 60509ffd..31e178fa 100644 --- a/src/components/BufferDetails.tsx +++ b/src/components/BufferDetails.tsx @@ -6,17 +6,16 @@ import { Icon, Intent } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import classNames from 'classnames'; import { Link } from 'react-router-dom'; -import { Tensor } from '../model/Graph'; import { OperationDescription, TensorData } from '../model/APIData'; import { toHex } from '../functions/math'; import ROUTES from '../definitions/routes'; import { useNextBuffer } from '../hooks/useAPI'; import 'styles/components/BufferDetails.scss'; +import getDeallocationOperation from '../functions/getDeallocationOperation'; interface BufferDetailsProps { tensor: TensorData; operations: OperationDescription[]; - queryKey: string; className?: string; } @@ -36,11 +35,11 @@ const HEADER_LABELS = { halo: 'Halo', }; -function BufferDetails({ tensor, operations, queryKey, className }: BufferDetailsProps) { +function BufferDetails({ tensor, operations, className }: BufferDetailsProps) { const { address, consumers, dtype, layout, shape } = tensor; const lastOperationId: number = tensor.consumers[tensor.consumers.length - 1]; const deallocationOperationId = getDeallocationOperation(tensor, operations); - const { data: buffer, isLoading } = useNextBuffer(address, consumers, queryKey); + const { data: buffer, isLoading } = useNextBuffer(address, consumers, tensor.id.toString()); return ( <> @@ -61,7 +60,7 @@ function BufferDetails({ tensor, operations, queryKey, className }: BufferDetail {isLoading ? 'Loading...' : undefined} - {buffer && !isLoading && deallocationOperationId ? ( + {buffer && !isLoading && Number.isFinite(deallocationOperationId) ? (
Deallocation found in{' '} @@ -167,16 +166,6 @@ function BufferDetails({ tensor, operations, queryKey, className }: BufferDetail ); } -function getDeallocationOperation(tensor: Tensor, operations: OperationDescription[]): number | undefined { - // TODO: Maybe we can strengthen this logic to ensure we're looking at deallocations rather than just checking the name - const matchingInputs = operations.filter( - (operation) => - operation.name.includes('deallocate') && operation.inputs.find((input) => input.id === tensor.id), - ); - - return matchingInputs.map((x) => x.id)[0]; -} - function parseMemoryConfig(string: string) { const regex = /MemoryConfig\((.*)\)$/; const match = string.match(regex); diff --git a/src/components/TensorList.tsx b/src/components/TensorList.tsx index 97762f1a..0a5ce112 100644 --- a/src/components/TensorList.tsx +++ b/src/components/TensorList.tsx @@ -306,7 +306,6 @@ const TensorList = () => {
diff --git a/src/components/operation-details/OperationDetailsComponent.tsx b/src/components/operation-details/OperationDetailsComponent.tsx index 7cf9d8eb..3c192947 100644 --- a/src/components/operation-details/OperationDetailsComponent.tsx +++ b/src/components/operation-details/OperationDetailsComponent.tsx @@ -5,7 +5,6 @@ import React, { Fragment, forwardRef, useRef, useState } from 'react'; import { Button, ButtonGroup, Icon, Intent, Switch } from '@blueprintjs/core'; import classNames from 'classnames'; -import { Link } from 'react-router-dom'; import { IconNames } from '@blueprintjs/icons'; import { toast } from 'react-toastify'; import { useAtom } from 'jotai'; @@ -17,7 +16,6 @@ import { isEqual } from '../../functions/math'; import StackTrace from './StackTrace'; import OperationDetailsNavigation from '../OperationDetailsNavigation'; import { OperationDetails } from '../../model/OperationDetails'; -import ROUTES from '../../definitions/routes'; import { BufferType } from '../../model/BufferType'; import { DRAM_MEMORY_SIZE } from '../../definitions/DRAMMemorySize'; import { @@ -27,13 +25,13 @@ import { PlotMouseEventCustom, } from '../../definitions/PlotConfigurations'; import { MemoryLegendElement } from './MemoryLegendElement'; -import Collapsible from '../Collapsible'; import OperationArguments from '../OperationArguments'; import { isDramActiveAtom, isL1ActiveAtom, selectedTensorAddressAtom } from '../../store/app'; import useOutsideClick from '../../hooks/useOutsideClick'; import { getBufferColor } from '../../functions/colorGenerator'; import ToastTensorMessage from './ToastTensorMessage'; import TensorDetailsComponent from './TensorDetailsComponent'; +import ProducerConsumersData from './ProducerConsumersData'; interface OperationDetailsProps { operationId: number; @@ -41,6 +39,7 @@ interface OperationDetailsProps { const MEMORY_ZOOM_PADDING_RATIO = 0.01; const DRAM_PADDING_RATIO = 0.9998; +const MAX_LEGEND_LENGTH = 20; const OperationDetailsComponent: React.FC = ({ operationId }) => { const { data: operations } = useOperationsList(); @@ -97,7 +96,7 @@ const OperationDetailsComponent: React.FC = ({ operationI const { chartData: previousChartData, memory: previousMemory } = previousDetails.memoryData(); const { chartData: dramData, memory: dramMemory } = details.memoryData(BufferType.DRAM); - const { chartData: previosDramData, memory: previousDramMemory } = previousDetails.memoryData(BufferType.DRAM); + const { chartData: previousDramData, memory: previousDramMemory } = previousDetails.memoryData(BufferType.DRAM); const memoryReport: FragmentationEntry[] = [...memory, ...fragmentation].sort((a, b) => a.address - b.address); const dramMemoryReport: FragmentationEntry[] = [...dramMemory].sort((a, b) => a.address - b.address); @@ -243,10 +242,6 @@ const OperationDetailsComponent: React.FC = ({ operationI const dramDeltaObject = details.getMemoryDelta(dramDelta, reverseDramDelta); - const dramTensorsOnly = dramMemoryReport.filter( - (chunk) => !chunk.empty && details.getTensorForAddress(chunk.address), - ); - // TODO: Look at refactoring this to avoid forwarding refs const ForwardedMemoryPlotRenderer = forwardRef(MemoryPlotRenderer); @@ -358,9 +353,37 @@ const OperationDetailsComponent: React.FC = ({ operationI configuration={L1RenderConfiguration} ref={(el) => assignRef(el, 1)} /> + +
MAX_LEGEND_LENGTH, + })} + > + {memoryReport.map((chunk) => ( + + ))} +
)} + {selectedTensorAddress && + selectedTensor && + (details.getTensorForAddress(selectedTensorAddress)?.buffer_type === BufferType.L1 || + details.getTensorForAddress(selectedTensorAddress)?.buffer_type === BufferType.L1_SMALL) ? ( + + ) : null} + {isDramActive && ( <>

DRAM

@@ -368,11 +391,11 @@ const OperationDetailsComponent: React.FC = ({ operationI = ({ operationI configuration={DRAMRenderConfiguration} ref={(el) => assignRef(el, 4)} /> - - )} -
-
- {isL1Active && - memoryReport.map((chunk) => ( +
MAX_LEGEND_LENGTH, + })} + > + {dramMemoryReport.map((chunk) => ( ))} - - {isDramActive && isL1Active &&
} - - {isDramActive && ( - <> - {/* // TODO: Refactor this because it looks weird when there are no tensors but there are DRAM buffers */} - {dramTensorsOnly.map((chunk) => ( - - ))} - - - {dramMemoryReport.map((chunk) => ( - - ))} - - - )} -
- -
assignRef(el, 5)} - className={classNames('producer-consumer', { hidden: selectedTensor === null })} - > -
- - Producers
- {details.getTensorProducerConsumer(selectedTensor).producers.map((op) => ( -
- {operationId === op.id ? ( - - {op.id} {op.name} - - ) : ( - - {op.id} {op.name} - - )} -
- ))} + + )} -
- {' '} - Consumers -
- {details.getTensorProducerConsumer(selectedTensor).consumers.map((op) => ( -
- {operationId === op.id ? ( - - {op.id} {op.name} - - ) : ( - - {op.id} {op.name} - - )} -
- ))} -
-
+ {selectedTensorAddress && + selectedTensor && + details.getTensorForAddress(selectedTensorAddress)?.buffer_type === BufferType.DRAM ? ( + + ) : null}
@@ -581,8 +509,10 @@ const OperationDetailsComponent: React.FC = ({ operationI {deviceOperation.cbList.length > 0 && (
MAX_LEGEND_LENGTH, + })} + style={{ marginLeft: `${deviceOperation.indentLevel * 2}em` }} > {deviceOperation.cbList.map((cb) => ( = ({ operationI )} {deviceOperation.deallocateAll && ( -

+

deallocate circular buffers

)} diff --git a/src/components/operation-details/ProducerConsumersData.tsx b/src/components/operation-details/ProducerConsumersData.tsx new file mode 100644 index 00000000..71f054e6 --- /dev/null +++ b/src/components/operation-details/ProducerConsumersData.tsx @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. + +import { Icon } from '@blueprintjs/core'; +import classNames from 'classnames'; +import { Link } from 'react-router-dom'; +import { IconNames } from '@blueprintjs/icons'; +import { useRef } from 'react'; +import ROUTES from '../../definitions/routes'; +import { OperationDetails } from '../../model/OperationDetails'; +import 'styles/components/ProducerConsumersData.scss'; + +interface ProducerConsumersDataProps { + selectedTensor: number; + details: OperationDetails; + operationId: number; +} + +function ProducerConsumersData({ selectedTensor, details, operationId }: ProducerConsumersDataProps) { + const outsideRefs = useRef([]); + + function assignRef(el: HTMLElement | null, index: number) { + if (el) { + outsideRefs.current[index] = el; + } + } + + return ( +
+
assignRef(el, 5)} + className={classNames('producer-consumer', { hidden: selectedTensor === null })} + > +
+ + Producers +
+ {details.getTensorProducerConsumer(selectedTensor).producers.map((op) => ( +
+ {operationId === op.id ? ( + + {op.id} {op.name} + + ) : ( + + {op.id} {op.name} + + )} +
+ ))} + +
+ {' '} + Consumers +
+ {details.getTensorProducerConsumer(selectedTensor).consumers.map((op) => ( +
+ {operationId === op.id ? ( + + {op.id} {op.name} + + ) : ( + + {op.id} {op.name} + + )} +
+ ))} +
+
+ ); +} + +export default ProducerConsumersData; diff --git a/src/components/operation-details/TensorDetailsComponent.tsx b/src/components/operation-details/TensorDetailsComponent.tsx index 39a6d62f..9906f32c 100644 --- a/src/components/operation-details/TensorDetailsComponent.tsx +++ b/src/components/operation-details/TensorDetailsComponent.tsx @@ -1,9 +1,13 @@ import React from 'react'; import classNames from 'classnames'; +import { Icon, Intent, PopoverPosition, Tooltip } from '@blueprintjs/core'; +import { IconNames } from '@blueprintjs/icons'; import { getBufferColor } from '../../functions/colorGenerator'; import { TensorData } from '../../model/APIData'; -import { prettyPrintAddress } from '../../functions/math'; +import { prettyPrintAddress, toHex } from '../../functions/math'; import { BufferTypeLabel } from '../../model/BufferType'; +import { useNextBuffer, useOperationsList } from '../../hooks/useAPI'; +import getDeallocationOperation from '../../functions/getDeallocationOperation'; export interface TensorDetailsComponentProps { tensor: TensorData; @@ -18,6 +22,11 @@ const TensorDetailsComponent: React.FC = ({ memorySize, onTensorClick, }) => { + const { id, address, consumers } = tensor; + const { data: operations } = useOperationsList(); + const { data: buffer, isLoading } = useNextBuffer(address, consumers, id.toString()); + const deallocationOperationId = operations ? getDeallocationOperation(tensor, operations) : null; + return (
= ({

Tensor ID: {tensor.id}

{prettyPrintAddress(tensor.address, memorySize)} + + {Number.isFinite(deallocationOperationId) && operations ? ( + operation.id === deallocationOperationId)?.name}`} + placement={PopoverPosition.TOP} + > + + + ) : ( + + + + )} + + {buffer?.next_usage && address && operations && !isLoading ? ( + operation.id === buffer.operation_id)?.name} (+${buffer.next_usage} operations)`} + placement={PopoverPosition.TOP} + > + + + ) : null}
diff --git a/src/functions/getDeallocationOperation.ts b/src/functions/getDeallocationOperation.ts new file mode 100644 index 00000000..e1be4fdc --- /dev/null +++ b/src/functions/getDeallocationOperation.ts @@ -0,0 +1,14 @@ +import { OperationDescription } from '../model/APIData'; +import { Tensor } from '../model/Graph'; + +function getDeallocationOperation(tensor: Tensor, operations: OperationDescription[]): number | undefined { + // TODO: Maybe we can strengthen this logic to ensure we're looking at deallocations rather than just checking the name + const matchingInputs = operations.filter( + (operation) => + operation.name.includes('deallocate') && operation.inputs.find((input) => input.id === tensor.id), + ); + + return matchingInputs.map((x) => x.id)[0]; +} + +export default getDeallocationOperation; diff --git a/src/model/OperationDetails.ts b/src/model/OperationDetails.ts index 2021e953..8b955c6d 100644 --- a/src/model/OperationDetails.ts +++ b/src/model/OperationDetails.ts @@ -264,10 +264,33 @@ ${tensor ? `

Tensor ${tensor.id}` : ''} size: 0, }, ].sort((a, b) => a.address - b.address); - totalMemory.forEach((chunk, index) => { + + const continuousMemory: Chunk[] = []; + totalMemory.forEach((chunk) => { + if (continuousMemory.length === 0) { + continuousMemory.push({ ...chunk }); + } else { + const lastChunk = continuousMemory[continuousMemory.length - 1]; + if (lastChunk.address + lastChunk.size >= chunk.address) { + lastChunk.size = Math.max(lastChunk.size, chunk.address + chunk.size - lastChunk.address); + } else { + continuousMemory.push({ ...chunk }); + } + } + }); + + continuousMemory.forEach((chunk, index) => { if (index > 0) { - const prevChunk = totalMemory[index - 1]; - if (prevChunk.address + prevChunk.size !== chunk.address) { + let prevChunkIndex = index - 1; + let prevChunk = continuousMemory[prevChunkIndex]; + + while (prevChunkIndex >= 0 && prevChunk.address + prevChunk.size > chunk.address) { + prevChunkIndex--; + if (prevChunkIndex >= 0) { + prevChunk = continuousMemory[prevChunkIndex]; + } + } + if (prevChunkIndex >= 0 && prevChunk.address + prevChunk.size < chunk.address) { fragmentation.push({ address: prevChunk.address + prevChunk.size, size: chunk.address - (prevChunk.address + prevChunk.size), @@ -277,9 +300,11 @@ ${tensor ? `

Tensor ${tensor.id}` : ''} } }); - const largestEmpty = fragmentation.reduce((prev, current) => { - return prev.size > current.size ? prev : current; - }); + const largestEmpty = fragmentation.length + ? fragmentation.reduce((prev, current) => { + return prev.size > current.size ? prev : current; + }) + : { size: 0 }; fragmentation.forEach((fragment) => { if (fragment.size === largestEmpty.size) { diff --git a/src/scss/components/OperationDetailsComponent.scss b/src/scss/components/OperationDetailsComponent.scss index 9431a95e..d0e6a62c 100644 --- a/src/scss/components/OperationDetailsComponent.scss +++ b/src/scss/components/OperationDetailsComponent.scss @@ -5,16 +5,6 @@ @use '../definitions/colours' as *; .operation-details-component { - .selected-tensor { - color: $tt-white; - - &::before { - display: inline-block; - content: '\2714'; // checkmark - margin-right: 5px; - } - } - .chart-controls { display: flex; gap: 10px; @@ -56,48 +46,18 @@ margin-bottom: 10px; } - .plot-tensor-details { - display: inline-grid; - grid-template-columns: 1fr auto; - align-items: start; - gap: 25px; - } - - .producer-consumer { - &.hidden { - display: none; - } - - .title { - font-size: 18px; - display: inline-flex; - align-items: center; - gap: 8px; - - &.hidden { - display: none; - } - } - - .operation-link { - margin-left: #{14px + 8px}; // icon width + gap - margin-bottom: 3px; - } - } - .legend { display: inline-flex; justify-content: space-between; margin-bottom: 20px; flex-direction: column; row-gap: 5px; + background-color: $tt-grey-3; + padding: 10px 20px; - hr { - margin: 10px 0; - } - - .full-dram-legend { - display: grid; + &.lengthy-legend { + height: 420px; + overflow-y: scroll; } .legend-item { @@ -124,7 +84,7 @@ .legend-details { display: inline-grid; - grid-template-columns: 1fr 1fr 0.75fr 1fr; + grid-template-columns: 1fr 1.2fr 0.75fr 1fr; gap: 15px; text-align: left; diff --git a/src/scss/components/ProducerConsumersData.scss b/src/scss/components/ProducerConsumersData.scss new file mode 100644 index 00000000..1e3dc351 --- /dev/null +++ b/src/scss/components/ProducerConsumersData.scss @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. + +@use '../definitions/colours' as *; + +.plot-tensor-details { + display: inline-grid; + grid-template-columns: 1fr auto; + align-items: start; + gap: 25px; + margin-left: 20px; + + .producer-consumer { + &.hidden { + display: none; + } + + .title { + font-size: 18px; + display: inline-flex; + align-items: center; + gap: 8px; + + &.hidden { + display: none; + } + } + + .operation-link { + margin-left: #{14px + 8px}; // icon width + gap + margin-bottom: 3px; + } + + .selected-tensor { + color: $tt-white; + + &::before { + display: inline-block; + content: '\2714'; // checkmark + margin-right: 5px; + } + } + } +}