From d913c47bbfb0846f5b2b019593b6b172926fe570 Mon Sep 17 00:00:00 2001 From: Saher-Amasha Date: Sun, 15 Jun 2025 00:39:39 +0300 Subject: [PATCH 1/5] update to local time logging with browser based visualization --- .vscode/launch.json | 5 +- README.md | 23 +- app.py | 23 -- controller.py | 15 -- example_usage/example1.py | 53 ----- index.html | 394 ++++++++++++++++++++++++++++++++++ inject_profiler.py | 218 +++++++++++++++++++ logic/metta_adder.py | 68 ------ logic/profiling_decorators.py | 35 --- logic/profiling_meta.py | 8 - logic/stamps/base_stamp.py | 10 - logic/stamps/memory_stamp.py | 29 --- logic/stamps/time_stamp.py | 30 --- main.py | 25 ++- profiler.js | 252 ++++++++++++++++++++++ profiler.py | 127 +++++++++++ profiler_server.py | 39 ---- requirements.txt | 1 + requirments.txt | 1 - resources/profiling_logo.png | Bin 90384 -> 0 bytes setup/build_deps.sh | 1 - setup_env.sh | 34 +++ view/base_view.py | 16 -- view/main_view.py | 76 ------- view/memory_view.py | 55 ----- view/time_view.py | 50 ----- 26 files changed, 1070 insertions(+), 518 deletions(-) delete mode 100644 app.py delete mode 100644 controller.py delete mode 100644 example_usage/example1.py create mode 100644 index.html create mode 100644 inject_profiler.py delete mode 100644 logic/metta_adder.py delete mode 100644 logic/profiling_decorators.py delete mode 100644 logic/profiling_meta.py delete mode 100644 logic/stamps/base_stamp.py delete mode 100644 logic/stamps/memory_stamp.py delete mode 100644 logic/stamps/time_stamp.py create mode 100644 profiler.js create mode 100644 profiler.py delete mode 100644 profiler_server.py create mode 100644 requirements.txt delete mode 100644 requirments.txt delete mode 100644 resources/profiling_logo.png delete mode 100644 setup/build_deps.sh create mode 100755 setup_env.sh delete mode 100644 view/base_view.py delete mode 100644 view/main_view.py delete mode 100644 view/memory_view.py delete mode 100644 view/time_view.py diff --git a/.vscode/launch.json b/.vscode/launch.json index 4254917..53a103f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,10 +1,9 @@ { "version": "0.2.0", - "configurations": [ - + "configurations": [ { "name": "main", - "type": "python", + "type": "debugpy", "request": "launch", "program": "main.py", "console": "integratedTerminal", diff --git a/README.md b/README.md index 9b0c4c1..1c3cbf7 100644 --- a/README.md +++ b/README.md @@ -1 +1,22 @@ -# PythonProfiler +# Python Profiler Viewer + +A **streaming log viewer and profiler visualizer** built for large-scale Python profiling data. + +--- + +## 🚀 Features + +- 🔄 **Streaming JSONL file processing** — processes log files while loading +- 🌳 **Virtualized expandable Call Tree** — smooth scroll & expand, even for millions of calls +- 📊 **Timeline view (Plotly powered)** — visual execution timeline +- 📈 **Summary table** — aggregated total time per function +- ⚡ **Fully browser-based** — no backend server required + +--- + +## 📄 Log Format + +Profiler expects newline-delimited JSON arrays (`.jsonl`), where each line represents one event: + +```json +["17:54:12.457", "start", "function_name", 123, 122, "sync"] \ No newline at end of file diff --git a/app.py b/app.py deleted file mode 100644 index cee096d..0000000 --- a/app.py +++ /dev/null @@ -1,23 +0,0 @@ - -import threading -from example_usage.example1 import Example -from profiler_server import ProfilerServer -from view.main_view import MainView - - - -class app: - server_instance = ProfilerServer() - MainWindow = MainView() - - @staticmethod - def run_test_mode(): - threading.Thread(target=app.server_instance.run).start() - threading.Thread(target=Example.test).start() - app.MainWindow.run() - - @staticmethod - def run(): - threading.Thread(target=app.server_instance.run).start() - app.MainWindow.run() - \ No newline at end of file diff --git a/controller.py b/controller.py deleted file mode 100644 index bc20479..0000000 --- a/controller.py +++ /dev/null @@ -1,15 +0,0 @@ -from datetime import datetime - - -class Model: - TIME_STAMPS = dict() - MEMORY_STAMPS = dict() - START_TIME= datetime.now() - @staticmethod - def add_time_stamp(id,val): - Model.TIME_STAMPS[id]= val - # MainWindow.update() - @staticmethod - def add_memmory_stamp(id,val): - Model.MEMORY_STAMPS[id]= val - # MainWindow.update() \ No newline at end of file diff --git a/example_usage/example1.py b/example_usage/example1.py deleted file mode 100644 index d98dd79..0000000 --- a/example_usage/example1.py +++ /dev/null @@ -1,53 +0,0 @@ -import random -import resource -from time import sleep - -from logic.profiling_meta import ProfilingMeta - - - -from resource import * -import time - -# a non CPU-bound task -time.sleep(3) - -class Example(metaclass=ProfilingMeta): - - @staticmethod - def ex1_func(): - a = [1] * (10 ** 6) - b = [1] * (10 ** 6) - print("1") - sleep(2) - Example.ex2_func() - sleep(3) - print("2") - @staticmethod - def ex2_func(): - print("1") - sleep(2) - print("2") - - @staticmethod - def ex3_func(): - a = [1] * (10 ** 7) - b = [1] * (10 ** 7) - print("1") - sleep(random.randint(0,5)) - print("2") - - @staticmethod - def test(): - i = 0 - while i < 10 : - sleep(random.randint(0,5)) - match random.randint(0,2): - case 0: - Example.ex1_func() - case 1: - Example.ex2_func() - case 2: - Example.ex3_func() - - i+=1 \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..7780672 --- /dev/null +++ b/index.html @@ -0,0 +1,394 @@ + + + + + + Python Profiler Viewer + + + + + + + + + + + + + + + + + +

Python Profiler Viewer

+ +
+ + +
+ +
+
+
Call Tree
+
+
+ + +
+ + + + + + + diff --git a/inject_profiler.py b/inject_profiler.py new file mode 100644 index 0000000..9459fa1 --- /dev/null +++ b/inject_profiler.py @@ -0,0 +1,218 @@ +""" +Code responsible for injecting the decorator into all .py files +""" + +import os +import sys +import ast +import shutil +import astor + +# Constant strings for marker and import line + +INJECTION_MARKER: str = "# PROFILER_INJECTED" + +# Excluded directories and files from injection process +EXCLUDED_DIRS = { + "venv", + "__pycache__", + "site-packages", + "env", + ".venv", + ".git", + "profiler_internal_files", +} +EXCLUDED_FILES = {"__init__.py", "setup.py", "profiler.py"} + + +class DecoratorInjector(ast.NodeTransformer): + """ + AST Transformer that injects the @profile decorator + into every function and async function. + """ + + def visit_FunctionDef(self, node: ast.FunctionDef) -> ast.FunctionDef: + """ + Visits all functions in the module + """ + if not any( + isinstance(d, ast.Name) and d.id == "profile" for d in node.decorator_list + ): + node.decorator_list.insert(0, ast.Name(id="profile", ctx=ast.Load())) + return self.generic_visit(node) + + def visit_AsyncFunctionDef( + self, node: ast.AsyncFunctionDef + ) -> ast.AsyncFunctionDef: + """ + Visits all async functions in the module + """ + if not any( + isinstance(d, ast.Name) and d.id == "profile" for d in node.decorator_list + ): + node.decorator_list.insert(0, ast.Name(id="profile", ctx=ast.Load())) + return self.generic_visit(node) + + +def should_process_file(filepath: str) -> bool: + """ + Determine if the file should be processed: + - Python file (.py) + - Not in excluded files + - Not already injected (check marker) + """ + filename = os.path.basename(filepath) + if filename in EXCLUDED_FILES or not filename.endswith(".py"): + return False + with open(filepath, "r", encoding="utf-8") as f: + first_line = f.readline() + if first_line.strip() == INJECTION_MARKER: + return False + return True + + +def copy_profiler_module(base_dir: str) -> None: + """ + Copy profiler.py into the target project directory if not already exists. + """ + dest_profiler_path = os.path.join(base_dir, "profiler.py") + if os.path.exists(dest_profiler_path): + print("profiler.py already exists in target project, skipping copy.") + return + + # Determine path of this script's directory + current_dir = os.path.dirname(os.path.abspath(__file__)) + src_profiler_path = os.path.join(current_dir, "profiler.py") + + if not os.path.exists(src_profiler_path): + print("ERROR: Cannot find profiler.py next to injector!") + sys.exit(1) + + shutil.copy2(src_profiler_path, dest_profiler_path) + print(f"Copied profiler.py into {base_dir}") + +def copy_file(base_dir: str,file_name) -> None: + """ + Copy profiler.py into the target project directory if not already exists. + """ + dest_profiler_path = os.path.join(base_dir, file_name) + if os.path.exists(dest_profiler_path): + print("file_name already exists in target project, skipping copy.") + return + + # Determine path of this script's directory + current_dir = os.path.dirname(os.path.abspath(__file__)) + src_profiler_path = os.path.join(current_dir, file_name) + + if not os.path.exists(src_profiler_path): + print(f"ERROR: Cannot find {file_name} next to injector!") + sys.exit(1) + + shutil.copy2(src_profiler_path, dest_profiler_path) + print(f"Copied profiler.py into {base_dir}") + + +def backup_file(filepath: str, base_dir: str, backup_dir: str) -> None: + """ + Backup the file to the backup directory before modification. + """ + rel_path = os.path.relpath(filepath, base_dir) + backup_path = os.path.join(backup_dir, rel_path) + os.makedirs(os.path.dirname(backup_path), exist_ok=True) + shutil.copy2(filepath, backup_path) + + +def process_file(filepath: str, profiler_import: str) -> None: + """ + Parse file, inject decorators, backup, and overwrite file. + """ + + with open(filepath, "r", encoding="utf-8") as f: + source = f.read() + + try: + tree = ast.parse(source) + except SyntaxError: + print(f"Skipping invalid file (syntax error): {filepath}") + return + + injector = DecoratorInjector() + new_tree = injector.visit(tree) + new_source = astor.to_source(new_tree) + + # Inject marker and import on top + new_source = INJECTION_MARKER + "\n" + profiler_import + "\n" + new_source + + with open(filepath, "w", encoding="utf-8") as f: + f.write(new_source) + + print(f"Injected: {filepath}") + + +def restore_backups(base_dir: str, backup_dir: str, internal_dir: str) -> None: + """ + Restore all backed-up files from backup directory to original state. + """ + if not os.path.exists(backup_dir): + print("No backup found.") + return + for root, _, files in os.walk(backup_dir): + for file in files: + src_path = os.path.join(root, file) + rel_path = os.path.relpath(src_path, backup_dir) + dest_path = os.path.join(base_dir, rel_path) + shutil.copy2(src_path, dest_path) + print(f"Restored: {dest_path}") + shutil.rmtree(internal_dir) + + +def create_backup_all(internal_files: str, base_dir: str): + """creates a backup for all files pre modification""" + # Handle backup + backup_dir = os.path.join(internal_files, "backup") + os.makedirs(backup_dir, exist_ok=True) + + original_files = [] + for root, dirs, files in os.walk(base_dir): + dirs[:] = [d for d in dirs if d not in EXCLUDED_DIRS] + for file in files: + full_path = os.path.join(root, file) + if should_process_file(full_path) and os.path.abspath( + full_path + ) != os.path.abspath(__file__): + original_files.append((full_path, base_dir, backup_dir)) + + for filepath, base_dir, backup_dir in original_files: + backup_file(filepath, base_dir, backup_dir) + + +def inject_all(base_dir_path: str) -> None: + """ + Walk through entire project directory and inject profiling + into all eligible Python files. + """ + # create internal dir + internal_files_dir_name = "profiler_internal_files" + internal_files_path = os.path.join(base_dir_path, internal_files_dir_name) + os.makedirs(internal_files_path, exist_ok=True) + + # Add profiler code + copy_profiler_module(internal_files_path) + + # Add ui code + copy_file(base_dir_path,"profiler.js") + copy_file(base_dir_path,"index.html") + # create backup + create_backup_all(internal_files_path, base_dir_path) + + # inject all files with decorator + profiler_import: str = f"from {internal_files_dir_name}.profiler import profile" + + for root, dirs, files in os.walk(base_dir_path): + dirs[:] = [d for d in dirs if d not in EXCLUDED_DIRS] + for file in files: + full_path = os.path.join(root, file) + if should_process_file(full_path) and os.path.abspath( + full_path + ) != os.path.abspath(__file__): + process_file(full_path, profiler_import) diff --git a/logic/metta_adder.py b/logic/metta_adder.py deleted file mode 100644 index e2a5af9..0000000 --- a/logic/metta_adder.py +++ /dev/null @@ -1,68 +0,0 @@ -import os -import re - -class MetaAdder: - def add_metaclass_to_files(self,root_folder): - # Loop through all files and subfiles in the current folder - for foldername, subfolders, filenames in os.walk(root_folder): - for filename in filenames: - # Check if the file has a .py extension - if filename.endswith('.py'): - file_path = os.path.join(foldername, filename) - - # Read the content of the file - with open(file_path, 'r') as file: - content = file.read() - - # Use regular expression to find and add metaclass to class definitions - content_with_metaclass = re.sub(r'class\s+(\w+)\(([^)]*)\)\s*:', r'class \1(\2, metaclass=MyMeta):', content) - - # Write the modified content back to the file - with open(file_path, 'w') as file: - file.write(content_with_metaclass) - - print("Metaclass added to all class definitions.") - - def remove_metaclass_from_files(self,root_folder): - # Loop through all files and subfiles in the current folder - for foldername, subfolders, filenames in os.walk(root_folder): - for filename in filenames: - # Check if the file has a .py extension - if filename.endswith('.py'): - file_path = os.path.join(foldername, filename) - - # Read the content of the file - with open(file_path, 'r') as file: - content = file.read() - - # Use regular expression to remove metaclass from class definitions - content_without_metaclass = re.sub(r'class\s+(\w+)\([^)]*,\s*metaclass=MyMeta\)\s*:', r'class \1:', content) - - # Write the modified content back to the file - with open(file_path, 'w') as file: - file.write(content_without_metaclass) - - print("Metaclass removed from all class definitions.") - -# # Ask the user whether to add or remove the metaclass -# action = input("Do you want to add or remove the metaclass? (Type 'add' or 'remove'): ").lower() - -# # Specify the root folder (current directory in this case) -# root_folder = os.getcwd() - -# if action == 'add': -# add_metaclass_to_files(root_folder) -# input("Metaclass added. Press Enter to continue.") -# elif action == 'remove': -# remove_metaclass_from_files(root_folder) -# input("Metaclass removed. Press Enter to continue.") -# else: -# print("Invalid action. Please type 'add' or 'remove'.") - - - - # Finished - # def[ ]+class[ ]+(.*)(:) , def class \1 (metaclass=MyMeta): - # def( )+class([ ]+.*?[ ]*)[ ]*([\)]*:) , def class \1 \2,metaclass=MyMeta): - - \ No newline at end of file diff --git a/logic/profiling_decorators.py b/logic/profiling_decorators.py deleted file mode 100644 index fefc23c..0000000 --- a/logic/profiling_decorators.py +++ /dev/null @@ -1,35 +0,0 @@ - -import functools -import os -import resource -import socket -from datetime import datetime -import uuid - -class ProfilingDecorators: - HOST = os.environ.get('HOST', "127.0.0.1") - PORT = int(os.environ.get('PORT', "65433") ) - - def time_profile(func): - @functools.wraps(func) - def wrapper(*args, **kwargs): - start = datetime.now() - val = func(*args, **kwargs) - end = datetime.now() - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.connect((ProfilingDecorators.HOST, ProfilingDecorators.PORT)) - s.sendall(bytes('time;'+str(uuid.uuid1()) +';'+str(func.__name__) +';'+ str(start) + ';'+str(end), 'utf-8')) - return val - return wrapper - def memory_profile(func): - @functools.wraps(func) - def wrapper(*args, **kwargs): - start = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss - val = func(*args, **kwargs) - end = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.connect((ProfilingDecorators.HOST, ProfilingDecorators.PORT)) - s.sendall(bytes('memory;'+str(uuid.uuid1()) +';'+str(func.__name__) +';'+ str(start) + ';'+str(end), 'utf-8')) - return val - return wrapper - \ No newline at end of file diff --git a/logic/profiling_meta.py b/logic/profiling_meta.py deleted file mode 100644 index be5aeb8..0000000 --- a/logic/profiling_meta.py +++ /dev/null @@ -1,8 +0,0 @@ -from logic.profiling_decorators import ProfilingDecorators - -class ProfilingMeta(type): - def __new__(cls, name, bases, class_dict): - for key, value in class_dict.items(): - if callable(value): - class_dict[key] = ProfilingDecorators.time_profile(ProfilingDecorators.memory_profile(value)) - return super().__new__(cls, name, bases, class_dict) diff --git a/logic/stamps/base_stamp.py b/logic/stamps/base_stamp.py deleted file mode 100644 index 72a5f47..0000000 --- a/logic/stamps/base_stamp.py +++ /dev/null @@ -1,10 +0,0 @@ -from dataclasses import dataclass - - -@dataclass -class BaseStamp(): - """ - object that holds a time stap and additional info about the function - """ - id:str - name:str \ No newline at end of file diff --git a/logic/stamps/memory_stamp.py b/logic/stamps/memory_stamp.py deleted file mode 100644 index 9719dda..0000000 --- a/logic/stamps/memory_stamp.py +++ /dev/null @@ -1,29 +0,0 @@ -from dataclasses import dataclass - -from logic.stamps.base_stamp import BaseStamp - - -@dataclass -class MemoryStamp(BaseStamp): - """ - object that holds a time stap and additional info about the function - """ - start_memory:float - end_memory:float - - @staticmethod - def init_from_bytes(split_desc_string): - """ - init date time from a string - """ - if len(split_desc_string) > 0: - - id = split_desc_string[0] - name = split_desc_string[1] - start_memory = float(split_desc_string[2]) - end_memory = float(split_desc_string[3]) - - return MemoryStamp(id=id,name=name,start_memory=start_memory,end_memory=end_memory) - - def __lt__(self, other): - return self.end_memory - self.start_memory < other.end_memory - other.start_memory \ No newline at end of file diff --git a/logic/stamps/time_stamp.py b/logic/stamps/time_stamp.py deleted file mode 100644 index ac10027..0000000 --- a/logic/stamps/time_stamp.py +++ /dev/null @@ -1,30 +0,0 @@ -from dataclasses import dataclass -from datetime import datetime - -from logic.stamps.base_stamp import BaseStamp - - -@dataclass -class TimeStamp(BaseStamp): - """ - object that holds a time stap and additional info about the function - """ - start:datetime - end:datetime - format:str ="%Y-%m-%d %H:%M:%S.%f" - - @staticmethod - def init_from_bytes(split_desc_string): - """ - init date time from a string - """ - if len(split_desc_string) > 0: - - id = split_desc_string[0] - name = split_desc_string[1] - start = datetime.strptime(split_desc_string[2], TimeStamp.format) - end = datetime.strptime(split_desc_string[3], TimeStamp.format) - - return TimeStamp(id=id,name=name,start=start,end=end) - def __lt__(self, other): - return self.start < other.start \ No newline at end of file diff --git a/main.py b/main.py index 092d557..771a8d2 100644 --- a/main.py +++ b/main.py @@ -1,10 +1,25 @@ -from app import app +""" +Main function of the profiler +""" +import os +import sys +from inject_profiler import inject_all, restore_backups -def main(): - app.run_test_mode() +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Usage:") + print(" Inject: python inject_profiler.py ") + print(" Restore: python inject_profiler.py restore") + sys.exit(1) + provided_base_dir: str = os.path.abspath(sys.argv[1]) + + generated_internal_dir: str = os.path.join(provided_base_dir, "profiler_internal_files") + generated_backup_dir: str = os.path.join(generated_internal_dir, "backup") -if __name__ == "__main__": - main() + if len(sys.argv) == 3 and sys.argv[2] == "restore": + restore_backups(provided_base_dir, generated_backup_dir, generated_internal_dir) + else: + inject_all(provided_base_dir) diff --git a/profiler.js b/profiler.js new file mode 100644 index 0000000..d2f155c --- /dev/null +++ b/profiler.js @@ -0,0 +1,252 @@ +// GLOBAL STATE +let calls = {}; +let idMap = {}; +let treeRoot = null; +let processedBatches = 0; +const renderEvery = 5; +const MAX_NODES = 5000; +const MAX_TIMELINE = 5000; + +// Streaming batch processing +function processBatch(batch) { + for (const entry of batch) { + const id = entry.call_id; + if (!calls[id]) { + calls[id] = { + call_id: id, + parent_call_id: entry.parent_call_id, + function: entry.function, + type: entry.type, + start: null, + end: null + }; + } + if (entry.event === "start") { + calls[id].start = parseTime(entry.time); + } else if (entry.event === "end") { + calls[id].end = parseTime(entry.time); + } + } + processedBatches++; +} + +function maybeRender() { + if (processedBatches % renderEvery === 0) { + renderPartial(); + } +} + +function renderPartial() { + const partialAgg = computeAggregates(Object.values(calls)); + renderSummary(partialAgg); +} + +function finalizeProcessing() { + idMap = buildCallMap(calls); + treeRoot = buildTree(idMap); + renderTree(treeRoot); + renderTimeline(Object.values(calls)); + renderSummary(computeAggregates(Object.values(calls))); + renderFlamegraph(buildFlamegraphTree(calls)); + if (treeRoot) showFunctionDetails(treeRoot); +} + +function parseTime(timeStr) { + const [h, m, s] = timeStr.split(':'); + const [sec, ms = "0"] = s.split('.'); + const date = new Date(); + date.setHours(parseInt(h), parseInt(m), parseInt(sec), parseInt(ms)); + return date; +} + +function buildCallMap(calls) { + const map = {}; + for (const id in calls) { + map[id] = { ...calls[id], children: [], expanded: false }; + } + for (const id in map) { + const node = map[id]; + if (node.parent_call_id != null && map[node.parent_call_id]) { + map[node.parent_call_id].children.push(node); + } + } + return map; +} +function flattenVisibleTree(node, result = [], depth = 0) { + result.push({ node, depth }); + if (node.expanded) { + for (const child of node.children) { + flattenVisibleTree(child, result, depth + 1); + } + } + return result; +} + +function buildTree(idMap) { + for (const id in idMap) { + if (idMap[id].parent_call_id == null || !idMap[idMap[id].parent_call_id]) { + return idMap[id]; + } + } + return null; +} + +function renderTree(treeData) { + if (!treeData) return; + + const container = document.getElementById("tree"); + container.innerHTML = ""; + + const table = document.createElement("table"); + table.className = "profiler-table"; + container.appendChild(table); + + const visibleNodes = flattenVisibleTree(treeData); + + for (const { node, depth } of visibleNodes) { + const row = table.insertRow(); + const cell = row.insertCell(); + + cell.style.paddingLeft = (depth * 20) + "px"; + + // Expand/collapse toggle + if (node.children.length > 0) { + const toggle = document.createElement("span"); + toggle.textContent = node.expanded ? "▼ " : "▶ "; + toggle.style.cursor = "pointer"; + toggle.onclick = () => { + node.expanded = !node.expanded; + renderTree(treeData); // re-render after toggle + }; + cell.appendChild(toggle); + } else { + cell.textContent = "• "; + } + + const label = document.createElement("span"); + label.textContent = `${node.function} (${node.call_id})`; + label.style.cursor = "pointer"; + label.onclick = () => showFunctionDetails(node); + cell.appendChild(label); + } +} +// === SAFE TIMELINE RENDERING === +function renderTimeline(callArray) { + const limited = callArray.slice(0, MAX_TIMELINE); + const bars = limited.map(call => { + let end = call.end || new Date(); + return { + x: [call.start, end], + y: [call.function + ` [${call.call_id}]`, call.function + ` [${call.call_id}]`], + mode: 'lines', + type: 'scatter', + name: `${call.function} [${call.call_id}]`, + call_id: call.call_id + }; + }); + + Plotly.newPlot('timeline', bars, { + title: 'Global Timeline', + xaxis: { type: 'date' }, + paper_bgcolor: '#1e1e1e', + plot_bgcolor: '#1e1e1e', + font: { color: '#dcdcdc' }, + height: document.getElementById("timeline").clientHeight + }); +} + +function showFunctionDetails(data) { + let html = ""; + html += ""; + html += ``; + html += ``; + if (data.parent_call_id && idMap[data.parent_call_id]) { + html += ``; + } + html += ``; + html += ``; + if (data.start && data.end) + html += ``; + else + html += ``; + html += "
Function Details
Function:${data.function}
Call ID:${data.call_id}
Parent:${idMap[data.parent_call_id].function} (${data.parent_call_id})
Start:${data.start || "missing"}
End:${data.end || "missing"}
Duration:${data.end - data.start} ms
Duration:incomplete
"; + document.getElementById("functionInfo").innerHTML = html; +} + +function computeAggregates(callArray) { + const agg = {}; + for (const call of callArray) { + if (!agg[call.function]) { + agg[call.function] = { count: 0, total: 0 }; + } + agg[call.function].count += 1; + if (call.start && call.end) { + agg[call.function].total += (call.end - call.start); + } + } + return agg; +} + +function renderSummary(aggregates) { + let html = ""; + html += ""; + const sorted = Object.entries(aggregates).sort((a, b) => b[1].total - a[1].total); + for (const [func, stat] of sorted) { + html += ``; + } + html += "
FunctionCallsTotal Time (ms)
${func}${stat.count}${stat.total}
"; + document.getElementById("summaryTable").innerHTML = html; +} + +function buildFlamegraphTree(calls) { + const root = { name: "__ROOT__", value: 0, children: [] }; + + for (const call of Object.values(calls)) { + let current = call; + const path = []; + while (current) { + path.unshift(current.function); + current = current.parent_call_id ? calls[current.parent_call_id] : null; + } + + let node = root; + for (const func of path) { + let child = node.children.find(c => c.name === func); + if (!child) { + child = { name: func, value: 0, children: [] }; + node.children.push(child); + } + node = child; + } + if (call.start && call.end) + node.value += call.end - call.start; + } + return root; +} + +function renderFlamegraph(data) { + document.getElementById("flamegraphView").innerHTML = ""; + const width = document.getElementById("flamegraphView").clientWidth; + const height = 400; + + const partition = d3.partition().size([width, height]); + const root = d3.hierarchy(data).sum(d => d.value); + partition(root); + + const color = d3.scaleOrdinal(d3.schemeTableau10); + + const svg = d3.select("#flamegraphView").append("svg") + .attr("width", width) + .attr("height", height); + + svg.selectAll("rect") + .data(root.descendants()) + .enter().append("rect") + .attr("x", d => d.x0) + .attr("y", d => d.y0) + .attr("width", d => d.x1 - d.x0) + .attr("height", d => d.y1 - d.y0) + .attr("fill", d => color(d.data.name)) + .append("title") + .text(d => `\${d.data.name}: \${Math.round(d.value)} ms`); +} diff --git a/profiler.py b/profiler.py new file mode 100644 index 0000000..0d9fc1f --- /dev/null +++ b/profiler.py @@ -0,0 +1,127 @@ +'''Main profiler code that will be injected''' +import functools as functoolsProfilerProtected +import inspect as inspectProfilerProtected +import json as jsonProfilerProtected +import threading as threadingProfilerProtected +import datetime as datetimeProfilerProtected +import os as osProfilerProtected +import itertools +from typing import Callable, Any, Dict, Optional + + +# Base directory to store the log file (default = current dir or env var override) +BASE_DIR: str = osProfilerProtected.getenv("PROFILER_BASE_DIR", osProfilerProtected.getcwd()) +LOG_FILE: str = osProfilerProtected.path.join(BASE_DIR, "profiler_log.jsonl") +LOCK: threadingProfilerProtected.Lock = threadingProfilerProtected.Lock() + +# Global unique call ID generator +CALL_ID_GENERATOR = itertools.count(1) + +# Thread-local call stack per thread +CALL_STACK = threadingProfilerProtected.local() + +def enter_function(function_name: str) -> Dict[str, Optional[int]]: + """ + Push current function onto thread-local call stack and return parent info. + """ + if not hasattr(CALL_STACK, 'stack'): + CALL_STACK.stack = [] + + call_id = next(CALL_ID_GENERATOR) + parent_call_id = CALL_STACK.stack[-1]['call_id'] if CALL_STACK.stack else None + + CALL_STACK.stack.append({ + 'function': function_name, + 'call_id': call_id + }) + + return {'call_id': call_id, 'parent_call_id': parent_call_id} + +def exit_function() -> None: + """ + Pop current function from call stack. + """ + if hasattr(CALL_STACK, 'stack') and CALL_STACK.stack: + CALL_STACK.stack.pop() + +def log_record(record: Dict[str, Any]) -> None: + """ + Thread-safe log writer. + """ + with LOCK: + with open(LOG_FILE, "a",encoding='utf-8') as f: + f.write(jsonProfilerProtected.dumps(record) + "\n") + +def profile(func: Callable[..., Any]) -> Callable[..., Any]: + """ + Decorator that wraps both synchronous and asynchronous functions + to log start and end events with call instance tracking. + """ + if inspectProfilerProtected.iscoroutinefunction(func): + @functoolsProfilerProtected.wraps(func) + async def async_wrapper(*args: Any, **kwargs: Any) -> Any: + context = enter_function(func.__qualname__) + call_id = context['call_id'] + parent_call_id = context['parent_call_id'] + now = datetimeProfilerProtected.datetime.now() + start_time = now.strftime('%H:%M:%S') + f".{now.microsecond // 1000:09d}" + log_record([ + start_time, + "start", + func.__qualname__, + call_id, + parent_call_id, + "async" + ]) + + try: + return await func(*args, **kwargs) + finally: + now = datetimeProfilerProtected.datetime.now() + end_time = now.strftime('%H:%M:%S') + f".{now.microsecond // 1000:09d}" + log_record([ + end_time, + "end", + func.__qualname__, + call_id, + parent_call_id, + "async" + ]) + exit_function() + + return async_wrapper + + else: + @functoolsProfilerProtected.wraps(func) + def sync_wrapper(*args: Any, **kwargs: Any) -> Any: + context = enter_function(func.__qualname__) + call_id = context['call_id'] + parent_call_id = context['parent_call_id'] + + now = datetimeProfilerProtected.datetime.now() + start_time = now.strftime('%H:%M:%S') + f".{now.microsecond // 1000:09d}" + log_record([ + start_time, + "start", + func.__qualname__, + call_id, + parent_call_id, + "sync" + ]) + + try: + return func(*args, **kwargs) + finally: + now = datetimeProfilerProtected.datetime.now() + end_time = now.strftime('%H:%M:%S') + f".{now.microsecond // 1000:09d}" + log_record([ + end_time, + "end", + func.__qualname__, + call_id, + parent_call_id, + "sync" + ]) + exit_function() + + return sync_wrapper diff --git a/profiler_server.py b/profiler_server.py deleted file mode 100644 index 596072d..0000000 --- a/profiler_server.py +++ /dev/null @@ -1,39 +0,0 @@ -import os -import socket -from controller import Model -from logic.stamps.memory_stamp import MemoryStamp - -from logic.stamps.time_stamp import TimeStamp - - - -class ProfilerServer: - HOST = os.environ.get('HOST', "127.0.0.1") - PORT = int(os.environ.get('PORT', "65433") ) - @staticmethod - def run(): - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.bind((ProfilerServer.HOST, ProfilerServer.PORT)) - s.listen() - while True: - conn, addr = s.accept() - with conn: - print(f"Connected by {addr}") - while True: - data = conn.recv(1024) - ProfilerServer.init_stamp_from_bytes(data) - if not data: - break - print(data) - - @staticmethod - def init_stamp_from_bytes(data): - split_desc_string = data.decode('utf-8').split(';') - if len(split_desc_string) > 0 : - match split_desc_string[0]: - case 'time': - Model.add_time_stamp(split_desc_string[1],TimeStamp.init_from_bytes(split_desc_string[1:])) - case 'memory': - # TODO FIX - Model.add_memmory_stamp(split_desc_string[1],MemoryStamp.init_from_bytes(split_desc_string[1:])) - diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..22a99d3 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +astor==0.8.1 \ No newline at end of file diff --git a/requirments.txt b/requirments.txt deleted file mode 100644 index 35c0477..0000000 --- a/requirments.txt +++ /dev/null @@ -1 +0,0 @@ -Pillow==10.1.0 \ No newline at end of file diff --git a/resources/profiling_logo.png b/resources/profiling_logo.png deleted file mode 100644 index ad718200f9c63c0f314c59625f037e68154583cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90384 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 diff --git a/setup/build_deps.sh b/setup/build_deps.sh deleted file mode 100644 index e494760..0000000 --- a/setup/build_deps.sh +++ /dev/null @@ -1 +0,0 @@ -sudo apt-get install python3-tk \ No newline at end of file diff --git a/setup_env.sh b/setup_env.sh new file mode 100755 index 0000000..e3ac6a7 --- /dev/null +++ b/setup_env.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +set -e + +VENV_DIR="venv" +REQUIREMENTS_FILE="requirements.txt" + +# Create venv if doesn't exist +if [ ! -d "$VENV_DIR" ]; then + echo "Creating virtual environment in ./$VENV_DIR ..." + python3 -m venv "$VENV_DIR" +else + echo "Virtual environment already exists in ./$VENV_DIR" +fi + +# Activate venv +if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "win32" || "$OSTYPE" == "cygwin" ]]; then + # Windows (Git Bash or native Bash) + source "$VENV_DIR/Scripts/activate" +else + # Unix/Linux/Mac + source "$VENV_DIR/bin/activate" +fi + +# Install requirements +if [ -f "$REQUIREMENTS_FILE" ]; then + echo "Installing requirements from $REQUIREMENTS_FILE ..." + pip install -r "$REQUIREMENTS_FILE" +else + echo "ERROR: $REQUIREMENTS_FILE not found." + exit 1 +fi + +echo "Environment setup complete." diff --git a/view/base_view.py b/view/base_view.py deleted file mode 100644 index 07b23ba..0000000 --- a/view/base_view.py +++ /dev/null @@ -1,16 +0,0 @@ -from dataclasses import dataclass - - -@dataclass -class Baseview: - width:int = 500 - height:int = 500 - color:str = '#FFFFFF' - canvas_scaler:int=40 - rectange_height:int = 60 - rectange_width:int = 20 - rectange_spacing:int = 5 - def draw_rect(self,x1,x2,y1,y2): - self.canvas.create_rectangle((x1,x2,y1,y2),fill='red') - def destroy(self): - self.frame.destroy() diff --git a/view/main_view.py b/view/main_view.py deleted file mode 100644 index 5451511..0000000 --- a/view/main_view.py +++ /dev/null @@ -1,76 +0,0 @@ -import tkinter -from PIL import ImageTk -from tkinter import * -from view.memory_view import MemoryView - -from view.time_view import TimeView - -class MainView(): - TITLE = "Python profiler" - CurrentWindow = None - def __init__(self) -> None: - self.root = tkinter.Tk() - - - # Set title - self.root.title=MainView.TITLE - - - # Set logo - logo = ImageTk.PhotoImage(file="resources/profiling_logo.png") - self.root.iconphoto(True,logo) - self.root.iconbitmap(default=None) - - - self.buttotns=[] - self.frames= [] - - # set buttons to move between views - self.time_button=tkinter.Button( self.root,text="time",command=self.time_window) - self.time_button.pack() - self.buttotns.append(self.time_button) - - - self.memoryButton=tkinter.Button( self.root,text="memory",command=self.memory_view) - self.memoryButton.pack() - self.buttotns.append(self.memoryButton) - - # self.networkButton=tkinter.Button( self.root,text="network",command=self.network_window) - # self.networkButton.grid(row=0,column=2,columnspan=5,sticky='w') - @staticmethod - def update(): - MainView.CurrentWindow.update() - def memory_view(self): - for button in self.buttotns: - button["state"] = "active" - for frame in self.frames: - frame.destroy() - - self.memory_view = MemoryView(self.root) - MainView.CurrentWindow = self.time_window - self.frames.append(self.memory_view) - self.memoryButton["state"] = "disabled" - self.memory_view.draw() - - def time_window(self): - for button in self.buttotns: - button["state"] = "active" - for frame in self.frames: - frame.destroy() - self.time_window = TimeView(self.root) - MainView.CurrentWindow = self.time_window - self.frames.append(self.time_window) - self.time_button["state"] = "disabled" - self.time_window.draw() - - - - # def network_window(self): - # for button in self.buttotns: - # button["state"] = "active" - # label = tkinter.Label(self.root,text="network profiling") - # label.grid(row=1,column=1) - - def run(self): - # Event loop - self.root.mainloop() \ No newline at end of file diff --git a/view/memory_view.py b/view/memory_view.py deleted file mode 100644 index 5ce7621..0000000 --- a/view/memory_view.py +++ /dev/null @@ -1,55 +0,0 @@ -from tkinter import * -from controller import Model - -from view.base_view import Baseview - - -class MemoryView(Baseview): - - def __init__(self,root) -> None: - self.root=root - self.frame=Frame(self.root,width=self.width,height=self.height) - label = Label(self.frame,text="Memory profiling") - copy = Model.MEMORY_STAMPS.copy() - - mlist = list(copy.values()) - if len(copy) > 0: - self.canvas=Canvas(self.frame,bg=self.color,width=self.width,height=self.height,scrollregion=(0,0,max(self.rectange_width*len(copy),self.width),max(max(mlist).end_memory - max(mlist).start_memory,self.height*self.canvas_scaler))) - else : - self.canvas=Canvas(self.frame,bg=self.color,width=self.width,height=self.height,scrollregion=(0,0,self.width*self.canvas_scaler,self.height*self.canvas_scaler)) - def draw(self): - self.frame.pack(expand=True, fill=BOTH) - - hbar=Scrollbar(self.frame,orient=HORIZONTAL) - hbar.pack(side=BOTTOM,fill=X) - hbar.config(command=self.canvas.xview) - vbar=Scrollbar(self.frame,orient=VERTICAL) - vbar.pack(side=RIGHT,fill=Y) - vbar.config(command=self.canvas.yview) - self.canvas.config(width=self.width,height=self.height) - self.canvas.config(xscrollcommand=hbar.set, yscrollcommand=vbar.set) - self.canvas.pack(side=LEFT,expand=True,fill=BOTH) - self.update() - - def update(self): - copy = Model.MEMORY_STAMPS.copy() - if len(copy) <=0: - return - mlist = list(copy.values()) - mlist_sorted= sorted(mlist) - max_mlist=max(mlist) - for index,i in enumerate(mlist_sorted): - memory_stamp = i - memory_leaked = memory_stamp.end_memory - memory_stamp.start_memory - if memory_leaked == 0 : - continue - self.scaler = 1/10 - x = index * (self.rectange_width + self.rectange_spacing) - y = (max_mlist.end_memory - max_mlist.start_memory) *self.scaler - width = x + self.rectange_width + 50 - height = memory_leaked *self.scaler# it is in kb - self.draw_rect( x ,y ,width ,height) - - self.canvas.create_text((x + width )/2 ,(y +height)/2 -self.rectange_height/3 , text=memory_stamp.name , fill="white") - self.canvas.create_text((x + width )/2 ,(y +height)/2 , text='memory_leaked: '+str(memory_leaked), fill="white") - self.canvas.create_text((x + width )/2 ,(y +height)/2 +self.rectange_height/3, text=memory_stamp.id, fill="white") \ No newline at end of file diff --git a/view/time_view.py b/view/time_view.py deleted file mode 100644 index fb864a4..0000000 --- a/view/time_view.py +++ /dev/null @@ -1,50 +0,0 @@ -from tkinter import * -from controller import Model -from view.base_view import Baseview - -class TimeView(Baseview): - def __init__(self,root) -> None: - self.root=root - self.frame=Frame(self.root,width=self.width,height=self.height) - label = Label(self.frame,text="time profiling") - label.pack() - self.canvas=Canvas(self.frame,bg=self.color,width=self.width,height=self.height,scrollregion=(0,0,self.width*self.canvas_scaler,self.height*self.canvas_scaler)) - def draw(self): - self.frame.pack(expand=True, fill=BOTH) - - hbar=Scrollbar(self.frame,orient=HORIZONTAL) - hbar.pack(side=BOTTOM,fill=X) - hbar.config(command=self.canvas.xview) - vbar=Scrollbar(self.frame,orient=VERTICAL) - vbar.pack(side=RIGHT,fill=Y) - vbar.config(command=self.canvas.yview) - self.canvas.config(width=self.width,height=self.height) - self.canvas.config(xscrollcommand=hbar.set, yscrollcommand=vbar.set) - self.canvas.pack(side=LEFT,expand=True,fill=BOTH) - self.update() - - def update(self): - copy = Model.TIME_STAMPS.copy() - if len(copy) <=0: - return - - mlist = list(copy.values()) - mlist_sorted= sorted(mlist) - # start =mlist_sorted[0].start - - for index,i in enumerate(mlist_sorted): - time_stamp = i - beggening = time_stamp.start - Model.START_TIME - time = time_stamp.end - time_stamp.start - self.scaler = 50 - # x, y, x+width, y+height, fill='red' - x = beggening.seconds *self.scaler - y = index * (self.rectange_height + self.rectange_spacing) - - width = x + (time.seconds ) *self.scaler - height = y + self.rectange_height - self.draw_rect( x ,y ,width ,height) - - self.canvas.create_text((x + width )/2 ,(y +height)/2 -self.rectange_height/3 , text=time_stamp.name , fill="white") - self.canvas.create_text((x + width )/2 ,(y +height)/2 , text=str(time.seconds)+' seconds', fill="white") - self.canvas.create_text((x + width )/2 ,(y +height)/2 +self.rectange_height/3, text=time_stamp.id, fill="white") From e8c615a0fccfa091a24c486ae90274439e7c1464 Mon Sep 17 00:00:00 2001 From: Saher-Amasha Date: Sun, 15 Jun 2025 00:43:58 +0300 Subject: [PATCH 2/5] update to local time logging with browser based visualization --- .github/workflows/pylint.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 383e65c..e44f0a7 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -18,6 +18,7 @@ jobs: run: | python -m pip install --upgrade pip pip install pylint + pip install -r requirements.txt - name: Analysing the code with pylint run: | pylint $(git ls-files '*.py') From 877c1312cdfed3c8cae8dddd4efb6eb8c6a5ecf2 Mon Sep 17 00:00:00 2001 From: Saher-Amasha Date: Sun, 15 Jun 2025 00:46:23 +0300 Subject: [PATCH 3/5] update to local time logging with browser based visualization --- inject_profiler.py | 6 +++--- profiler.py | 52 +++++++++++++++++++++------------------------- 2 files changed, 27 insertions(+), 31 deletions(-) diff --git a/inject_profiler.py b/inject_profiler.py index 9459fa1..932f4e4 100644 --- a/inject_profiler.py +++ b/inject_profiler.py @@ -182,8 +182,8 @@ def create_backup_all(internal_files: str, base_dir: str): ) != os.path.abspath(__file__): original_files.append((full_path, base_dir, backup_dir)) - for filepath, base_dir, backup_dir in original_files: - backup_file(filepath, base_dir, backup_dir) + for filepath, c_base_dir, backup_dir in original_files: + backup_file(filepath, c_base_dir, backup_dir) def inject_all(base_dir_path: str) -> None: @@ -199,7 +199,7 @@ def inject_all(base_dir_path: str) -> None: # Add profiler code copy_profiler_module(internal_files_path) - # Add ui code + # Add ui code copy_file(base_dir_path,"profiler.js") copy_file(base_dir_path,"index.html") # create backup diff --git a/profiler.py b/profiler.py index 0d9fc1f..286c38f 100644 --- a/profiler.py +++ b/profiler.py @@ -91,37 +91,33 @@ async def async_wrapper(*args: Any, **kwargs: Any) -> Any: return async_wrapper - else: - @functoolsProfilerProtected.wraps(func) - def sync_wrapper(*args: Any, **kwargs: Any) -> Any: - context = enter_function(func.__qualname__) - call_id = context['call_id'] - parent_call_id = context['parent_call_id'] - + @functoolsProfilerProtected.wraps(func) + def sync_wrapper(*args: Any, **kwargs: Any) -> Any: + context = enter_function(func.__qualname__) + call_id = context['call_id'] + parent_call_id = context['parent_call_id'] + now = datetimeProfilerProtected.datetime.now() + start_time = now.strftime('%H:%M:%S') + f".{now.microsecond // 1000:09d}" + log_record([ + start_time, + "start", + func.__qualname__, + call_id, + parent_call_id, + "sync" + ]) + try: + return func(*args, **kwargs) + finally: now = datetimeProfilerProtected.datetime.now() - start_time = now.strftime('%H:%M:%S') + f".{now.microsecond // 1000:09d}" + end_time = now.strftime('%H:%M:%S') + f".{now.microsecond // 1000:09d}" log_record([ - start_time, - "start", + end_time, + "end", func.__qualname__, call_id, parent_call_id, "sync" - ]) - - try: - return func(*args, **kwargs) - finally: - now = datetimeProfilerProtected.datetime.now() - end_time = now.strftime('%H:%M:%S') + f".{now.microsecond // 1000:09d}" - log_record([ - end_time, - "end", - func.__qualname__, - call_id, - parent_call_id, - "sync" - ]) - exit_function() - - return sync_wrapper + ]) + exit_function() + return sync_wrapper From 2bc4ebf47ff2f5550076b5ee0608cc1bfc09f244 Mon Sep 17 00:00:00 2001 From: Saher-Amasha Date: Sun, 15 Jun 2025 00:47:56 +0300 Subject: [PATCH 4/5] fix pylint formatting issue --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 771a8d2..a922b10 100644 --- a/main.py +++ b/main.py @@ -15,7 +15,7 @@ sys.exit(1) provided_base_dir: str = os.path.abspath(sys.argv[1]) - + generated_internal_dir: str = os.path.join(provided_base_dir, "profiler_internal_files") generated_backup_dir: str = os.path.join(generated_internal_dir, "backup") From e53b293aa31043c74d3a2ae94fbe29983e67f20d Mon Sep 17 00:00:00 2001 From: Saher-Amasha Date: Sun, 15 Jun 2025 00:50:11 +0300 Subject: [PATCH 5/5] fix pylint formatting issue --- inject_profiler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inject_profiler.py b/inject_profiler.py index 932f4e4..802f2a8 100644 --- a/inject_profiler.py +++ b/inject_profiler.py @@ -31,7 +31,7 @@ class DecoratorInjector(ast.NodeTransformer): into every function and async function. """ - def visit_FunctionDef(self, node: ast.FunctionDef) -> ast.FunctionDef: + def visit_FunctionDef(self, node: ast.FunctionDef) -> ast.FunctionDef: # pylint: disable=invalid-name """ Visits all functions in the module """ @@ -41,7 +41,7 @@ def visit_FunctionDef(self, node: ast.FunctionDef) -> ast.FunctionDef: node.decorator_list.insert(0, ast.Name(id="profile", ctx=ast.Load())) return self.generic_visit(node) - def visit_AsyncFunctionDef( + def visit_AsyncFunctionDef( # pylint: disable=invalid-name self, node: ast.AsyncFunctionDef ) -> ast.AsyncFunctionDef: """