mirror of
https://github.com/facebook/sapling.git
synced 2024-10-12 09:48:05 +03:00
1a84d01de5
Summary: Needed for windows to allow us write to a tempfile created in Rust. Should do it in a different way? Context: we create a NameTempFile from Rust that we pass as argument to hg as profiling.output=named_temp_file. When open with 'w', the mercurial command fails with "abort: Permission denied". As far as I read, it might be because of how the Win32 API works. Under the hood, Python's open function is calling the CreateFile function, and if that fails, it translates the Windows error code into a Python IOError. From CreateFile documentation: If CREATE_ALWAYS and FILE_ATTRIBUTE_NORMAL are specified, CreateFile fails and sets the last error to ERROR_ACCESS_DENIED if the file exists and has the FILE_ATTRIBUTE_HIDDEN or FILE_ATTRIBUTE_SYSTEM attribute. To avoid the error, specify the same attributes as the existing file. I might be wrong though... any other suggestions? Reviewed By: ikostia Differential Revision: D8099420 fbshipit-source-id: c3077cc5d7bc03c8f2eeafdac3467db62c20a669
263 lines
8.1 KiB
Python
263 lines
8.1 KiB
Python
# profiling.py - profiling functions
|
|
#
|
|
# Copyright 2016 Gregory Szorc <gregory.szorc@gmail.com>
|
|
#
|
|
# This software may be used and distributed according to the terms of the
|
|
# GNU General Public License version 2 or any later version.
|
|
|
|
from __future__ import absolute_import, print_function
|
|
|
|
import contextlib
|
|
import errno
|
|
|
|
from . import encoding, error, extensions, pycompat, util
|
|
from .i18n import _
|
|
|
|
|
|
def _loadprofiler(ui, profiler):
|
|
"""load profiler extension. return profile method, or None on failure"""
|
|
extname = profiler
|
|
extensions.loadall(ui, whitelist=[extname])
|
|
try:
|
|
mod = extensions.find(extname)
|
|
except KeyError:
|
|
return None
|
|
else:
|
|
return getattr(mod, "profile", None)
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def lsprofile(ui, fp):
|
|
format = ui.config("profiling", "format")
|
|
field = ui.config("profiling", "sort")
|
|
limit = ui.configint("profiling", "limit")
|
|
climit = ui.configint("profiling", "nested")
|
|
|
|
if format not in ["text", "kcachegrind"]:
|
|
ui.warn(_("unrecognized profiling format '%s'" " - Ignored\n") % format)
|
|
format = "text"
|
|
|
|
try:
|
|
from . import lsprof
|
|
except ImportError:
|
|
raise error.Abort(
|
|
_(
|
|
"lsprof not available - install from "
|
|
"http://codespeak.net/svn/user/arigo/hack/misc/lsprof/"
|
|
)
|
|
)
|
|
p = lsprof.Profiler()
|
|
p.enable(subcalls=True)
|
|
try:
|
|
yield
|
|
finally:
|
|
p.disable()
|
|
|
|
if format == "kcachegrind":
|
|
from . import lsprofcalltree
|
|
|
|
calltree = lsprofcalltree.KCacheGrind(p)
|
|
calltree.output(fp)
|
|
else:
|
|
# format == 'text'
|
|
stats = lsprof.Stats(p.getstats())
|
|
stats.sort(field)
|
|
stats.pprint(limit=limit, file=fp, climit=climit)
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def flameprofile(ui, fp):
|
|
try:
|
|
from flamegraph import flamegraph
|
|
except ImportError:
|
|
raise error.Abort(
|
|
_(
|
|
"flamegraph not available - install from "
|
|
"https://github.com/evanhempel/python-flamegraph"
|
|
)
|
|
)
|
|
# developer config: profiling.freq
|
|
freq = ui.configint("profiling", "freq")
|
|
filter_ = None
|
|
collapse_recursion = True
|
|
thread = flamegraph.ProfileThread(fp, 1.0 / freq, filter_, collapse_recursion)
|
|
start_time = util.timer()
|
|
try:
|
|
thread.start()
|
|
yield
|
|
finally:
|
|
thread.stop()
|
|
thread.join()
|
|
print(
|
|
"Collected %d stack frames (%d unique) in %2.2f seconds."
|
|
% (
|
|
util.timer() - start_time,
|
|
thread.num_frames(),
|
|
thread.num_frames(unique=True),
|
|
)
|
|
)
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def statprofile(ui, fp):
|
|
from . import statprof
|
|
|
|
freq = ui.configint("profiling", "freq")
|
|
if freq > 0:
|
|
# Cannot reset when profiler is already active. So silently no-op.
|
|
if statprof.state.profile_level == 0:
|
|
statprof.reset(freq)
|
|
else:
|
|
ui.warn(_("invalid sampling frequency '%s' - ignoring\n") % freq)
|
|
|
|
statprof.start(mechanism="thread")
|
|
|
|
try:
|
|
yield
|
|
finally:
|
|
data = statprof.stop()
|
|
|
|
profformat = ui.config("profiling", "statformat")
|
|
|
|
formats = {
|
|
"byline": statprof.DisplayFormats.ByLine,
|
|
"bymethod": statprof.DisplayFormats.ByMethod,
|
|
"hotpath": statprof.DisplayFormats.Hotpath,
|
|
"json": statprof.DisplayFormats.Json,
|
|
"chrome": statprof.DisplayFormats.Chrome,
|
|
}
|
|
|
|
if profformat in formats:
|
|
displayformat = formats[profformat]
|
|
else:
|
|
ui.warn(_("unknown profiler output format: %s\n") % profformat)
|
|
displayformat = statprof.DisplayFormats.Hotpath
|
|
|
|
kwargs = {}
|
|
|
|
def fraction(s):
|
|
if isinstance(s, (float, int)):
|
|
return float(s)
|
|
if s.endswith("%"):
|
|
v = float(s[:-1]) / 100
|
|
else:
|
|
v = float(s)
|
|
if 0 <= v <= 1:
|
|
return v
|
|
raise ValueError(s)
|
|
|
|
if profformat == "chrome":
|
|
showmin = ui.configwith(fraction, "profiling", "showmin", 0.005)
|
|
showmax = ui.configwith(fraction, "profiling", "showmax")
|
|
kwargs.update(minthreshold=showmin, maxthreshold=showmax)
|
|
elif profformat == "hotpath":
|
|
# inconsistent config: profiling.showmin
|
|
limit = ui.configwith(fraction, "profiling", "showmin", 0.05)
|
|
kwargs["limit"] = limit
|
|
|
|
statprof.display(fp, data=data, format=displayformat, **kwargs)
|
|
|
|
|
|
class profile(object):
|
|
"""Start profiling.
|
|
|
|
Profiling is active when the context manager is active. When the context
|
|
manager exits, profiling results will be written to the configured output.
|
|
"""
|
|
|
|
def __init__(self, ui, enabled=True):
|
|
self._ui = ui
|
|
self._output = None
|
|
self._fp = None
|
|
self._fpdoclose = True
|
|
self._profiler = None
|
|
self._enabled = enabled
|
|
self._entered = False
|
|
self._started = False
|
|
|
|
def __enter__(self):
|
|
self._entered = True
|
|
if self._enabled:
|
|
self.start()
|
|
return self
|
|
|
|
def start(self):
|
|
"""Start profiling.
|
|
|
|
The profiling will stop at the context exit.
|
|
|
|
If the profiler was already started, this has no effect."""
|
|
if not self._entered:
|
|
raise error.ProgrammingError()
|
|
if self._started:
|
|
return
|
|
self._started = True
|
|
profiler = encoding.environ.get("HGPROF")
|
|
proffn = None
|
|
if profiler is None:
|
|
profiler = self._ui.config("profiling", "type")
|
|
if profiler not in ("ls", "stat", "flame"):
|
|
# try load profiler from extension with the same name
|
|
proffn = _loadprofiler(self._ui, profiler)
|
|
if proffn is None:
|
|
self._ui.warn(_("unrecognized profiler '%s' - ignored\n") % profiler)
|
|
profiler = "stat"
|
|
|
|
self._output = self._ui.config("profiling", "output")
|
|
|
|
try:
|
|
if self._output == "blackbox":
|
|
self._fp = util.stringio()
|
|
elif self._output:
|
|
path = self._ui.expandpath(self._output)
|
|
try:
|
|
self._fp = open(path, "wb")
|
|
except IOError as e:
|
|
# Needed for the case when we create a tempfile
|
|
# in Rust and then we try to write to that file.
|
|
# Safe since we only run this if the previous
|
|
# open failed and if it is on windows and we get
|
|
# the Permission Denied error.
|
|
# See D8099420.
|
|
if pycompat.iswindows and e.errno == errno.EACCES:
|
|
self._fp = open(path, "r+b")
|
|
else:
|
|
raise
|
|
else:
|
|
self._fpdoclose = False
|
|
self._fp = self._ui.ferr
|
|
|
|
if proffn is not None:
|
|
pass
|
|
elif profiler == "ls":
|
|
proffn = lsprofile
|
|
elif profiler == "flame":
|
|
proffn = flameprofile
|
|
else:
|
|
proffn = statprofile
|
|
|
|
self._profiler = proffn(self._ui, self._fp)
|
|
self._profiler.__enter__()
|
|
except: # re-raises
|
|
self._closefp()
|
|
raise
|
|
|
|
def __exit__(self, exception_type, exception_value, traceback):
|
|
propagate = None
|
|
if self._profiler is not None:
|
|
propagate = self._profiler.__exit__(
|
|
exception_type, exception_value, traceback
|
|
)
|
|
if self._output == "blackbox":
|
|
val = "Profile:\n%s" % self._fp.getvalue()
|
|
# ui.log treats the input as a format string,
|
|
# so we need to escape any % signs.
|
|
val = val.replace("%", "%%")
|
|
self._ui.log("profile", val)
|
|
self._closefp()
|
|
return propagate
|
|
|
|
def _closefp(self):
|
|
if self._fpdoclose and self._fp is not None:
|
|
self._fp.close()
|