Skip to content

Commit

Permalink
Fixes browser detection; some refactoring (#737)
Browse files Browse the repository at this point in the history
* Changed to whitelist graphical browsers.

* Factored out flamegraph_format and generate_html.

* Fixed Windows browser name.
  • Loading branch information
emeryberger authored Dec 10, 2023
1 parent bf1a8f0 commit b0b2cef
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 61 deletions.
19 changes: 11 additions & 8 deletions scalene/find_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@

def find_browser() -> Optional[webbrowser.BaseBrowser]:
"""Find the default browser if possible and if compatible."""

# Mostly taken from the standard library webbrowser module. Search "console browsers" in there.
# In general, a browser belongs on this list of the scalene web GUI doesn't work in it.
# See https://github.com/plasma-umass/scalene/issues/723.
incompatible_browsers = {"www-browser", "links", "elinks", "lynx", "w3m", "links2", "links-g"}

# Names of known graphical browsers as per Python's webbrowser documentation
graphical_browsers = ["windowsdefault", "macosx", "safari", "google-chrome",
"chrome", "chromium", "firefox", "opera", "edge", "mozilla", "netscape"]
try:
# Get the default browser object
browser = webbrowser.get()
# Check if the browser's class name matches any of the known graphical browsers
browser_class_name = str(type(browser)).lower()
if any(graphical_browser in browser_class_name for graphical_browser in graphical_browsers):
return browser
else:
return None
except webbrowser.Error:
# Return None if there is an error in getting the browser
return None

return None if browser.name in incompatible_browsers else browser
57 changes: 4 additions & 53 deletions scalene/scalene_profiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
from collections import defaultdict
from importlib.abc import SourceLoader
from importlib.machinery import ModuleSpec
from jinja2 import Environment, FileSystemLoader
from types import CodeType, FrameType
from typing import (
Any,
Expand Down Expand Up @@ -843,23 +842,11 @@ def cpu_signal_handler(
if sys.platform == "win32":
Scalene.__windows_queue.put(None)

@staticmethod
def flamegraph_format() -> str:
"""Converts stacks to a string suitable for input to Brendan Gregg's flamegraph.pl script."""
output = ""
for stk in Scalene.__stats.stacks.keys():
for item in stk:
(fname, fn_name, lineno) = item
output += f"{fname} {fn_name}:{lineno};"
output += " " + str(Scalene.__stats.stacks[stk])
output += "\n"
return output

@staticmethod
def output_profile(program_args: Optional[List[str]] = None) -> bool:
"""Output the profile. Returns true iff there was any info reported the profile."""
# sourcery skip: inline-immediately-returned-variable
# print(Scalene.flamegraph_format())
# print(Scalene.flamegraph_format(Scalene.__stats.stacks))
if Scalene.__args.json:
json_output = Scalene.__json.output_profiles(
Scalene.__program_being_profiled,
Expand Down Expand Up @@ -1611,8 +1598,8 @@ def stop() -> None:
if Scalene.__args.outfile:
Scalene.__profile_filename = os.path.join(os.path.dirname(Scalene.__args.outfile),
os.path.basename(Scalene.__profile_filename))
if (
Scalene.__args.web

if (Scalene.__args.web
and not Scalene.__args.cli
and not Scalene.__is_child
):
Expand Down Expand Up @@ -1716,42 +1703,6 @@ def exit_handler() -> None:
with contextlib.suppress(Exception):
os.remove(f"/tmp/scalene-malloc-lock{os.getpid()}")

@staticmethod
def generate_html(profile_fname: Filename, output_fname: Filename) -> None:
"""Apply a template to generate a single HTML payload containing the current profile."""

try:
# Load the profile
profile_file = pathlib.Path(profile_fname)
profile = profile_file.read_text()
except FileNotFoundError:
return

# Load the GUI JavaScript file.
scalene_dir = os.path.dirname(__file__)
gui_fname = os.path.join(scalene_dir, "scalene-gui", "scalene-gui.js")
gui_file = pathlib.Path(gui_fname)
gui_js = gui_file.read_text()

# Put the profile and everything else into the template.
environment = Environment(
loader=FileSystemLoader(os.path.join(scalene_dir, "scalene-gui"))
)
template = environment.get_template("index.html.template")
rendered_content = template.render(
profile=profile,
gui_js=gui_js,
scalene_version=scalene_version,
scalene_date=scalene_date,
)

# Write the rendered content to the specified output file.
try:
with open(output_fname, "w", encoding="utf-8") as f:
f.write(rendered_content)
except OSError:
pass

def profile_code(
self,
code: str,
Expand Down Expand Up @@ -1809,7 +1760,7 @@ def profile_code(
):
return exit_status

Scalene.generate_html(
generate_html(
profile_fname=Scalene.__profile_filename,
output_fname=Scalene.__args.outfile if Scalene.__args.outfile else Scalene.__profiler_html,
)
Expand Down
51 changes: 51 additions & 0 deletions scalene/scalene_utility.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import inspect
import os
import pathlib
import sys

from jinja2 import Environment, FileSystemLoader
from types import FrameType
from typing import (
Any,
Expand All @@ -14,6 +18,7 @@
Filename,
LineNumber
)
from scalene.scalene_version import scalene_version, scalene_date

# These are here to simplify print debugging, a la C.
class LineNo:
Expand Down Expand Up @@ -87,3 +92,49 @@ def get_fully_qualified_name(frame: FrameType) -> Filename:
break
f = f.f_back
return fn_name

def flamegraph_format(stacks: Dict[Tuple[Any], int]) -> str:
"""Converts stacks to a string suitable for input to Brendan Gregg's flamegraph.pl script."""
output = ""
for stk in stacks.keys():
for item in stk:
(fname, fn_name, lineno) = item
output += f"{fname} {fn_name}:{lineno};"
output += " " + str(stacks[stk])
output += "\n"
return output

def generate_html(profile_fname: Filename, output_fname: Filename) -> None:
"""Apply a template to generate a single HTML payload containing the current profile."""

try:
# Load the profile
profile_file = pathlib.Path(profile_fname)
profile = profile_file.read_text()
except FileNotFoundError:
return

# Load the GUI JavaScript file.
scalene_dir = os.path.dirname(__file__)
gui_fname = os.path.join(scalene_dir, "scalene-gui", "scalene-gui.js")
gui_file = pathlib.Path(gui_fname)
gui_js = gui_file.read_text()

# Put the profile and everything else into the template.
environment = Environment(
loader=FileSystemLoader(os.path.join(scalene_dir, "scalene-gui"))
)
template = environment.get_template("index.html.template")
rendered_content = template.render(
profile=profile,
gui_js=gui_js,
scalene_version=scalene_version,
scalene_date=scalene_date,
)

# Write the rendered content to the specified output file.
try:
with open(output_fname, "w", encoding="utf-8") as f:
f.write(rendered_content)
except OSError:
pass

0 comments on commit b0b2cef

Please sign in to comment.