Package: ox_profile
IntroductionTheox_profilepackage provides a python framework for statistical
profiling. If you are usingFlask, thenox_profileprovides a
flask blueprint so that you can start/stop/analyze profiling from within
your application. You can also run the profiler stand-alone withoutFlaskas well.To learn more, you can browse the overview slides inPDF
formor read more details below.Why statistical profiling (and why ox_profile)?Python contains many profilers which instrument your code and give you
exact results. A main benefit here is you knowexactlywhat your
program is doing. The disadvantage is that there can be significant
overhead. With a statistical profiler such asox_profile, we sample
a running program periodically to get a sense of what the program is
doing with an overhead that can be tuned as desired.One main use case forox_profilespecifically (and statistical
profiling in general) is that you can apply it to a production server to
see how things work “in the wild”.There are other statistical profilers out there for python (such as
statprof), which are pretty good and may be better for your needs thanox_profile. So why would you considerox_profile? Some possible
reasons include:Works on non-UNIX systems (e.g., works on Windows).Many other statistical profilers use various excellent features of
LINUX or UNIX whileox_profileonly really relies on the
pythonsys._current_framesmethod.Simple to understand.The code forox_profileis fairly simple. The main work is
really insideox_profile.core.sampling.Samplerso it is easy
to reason about or modify if you need slightly different
profiling.Flask Blueprint provided.If you are using Flask, then you can register theox_profileblueprint and easily get statistical profiling in your flask app.UsageStand aloneYou can installox_profileusing pip via something like$pipinstallox_profileThe simplest way to run the profiler is by starting the launcher,
calling some functions, and the printing the profiled data via something
like:>>> from ox_profile.core.launchers import SimpleLauncher
>>> profiler = SimpleLauncher.launch()      # Create and start a profiler.
>>> # call some functions
>>> print(profiler.show())                  # Print current results in preformated way.
>>> profiler.cancel()                       # Turns off the profiler for good.Often you may want a slightly more sophisticated use case where you can
pause and unpause the profiler and get more details about its status as
shown below:>>> from ox_profile.core import launchers
>>> profiler = launchers.SimpleLauncher()    # Create an instance of launcher to be your profiler
>>> profiler.start()                         # The profiler is a thread so we need to call start
>>> profiler.unpause()                       # The profiler starts out paused so we unpause it
>>> # call functions or start main program
>>> profiler.pause()                         # You can pause if done profiling or leave running
>>> query, total_records = profiler.query()  # Query for what the profiler has found
>>> info = ['%s: %s' % (i.name, i.hits) for i in query]
>>> print('Items in query:\n  - %s' % (('\n  - '.join(info))))
>>> profiler.cancel()                        # This turns off the profiler for goodWith FlaskIf you are using the python flask framework and have installedox_profile(e.g., withpip install ox_profile) then you can
simply do the following in the appropriate place after initializing your
app:from ox_profile.ui.flask.views import OX_PROF_BP
app.register_blueprint(OX_PROF_BP)
app.config['OX_PROF_USERS'] = {<admin_user_1>, <admin_user_2>, ...}where<admin_user_>, etc. are strings referring to users who are
allowed to accessox_profile. By default, we checkcurrent_user.name, but you can set theOX_PROF_USERNAME_FIELDinapp.configto something else (e.g.,'email'to choose which
field ofcurrent_useris checked against the sequence of strings inOX_PROF_USERS.Pointing your browser to the route/ox_profile/statuswill then show
you the profiling status. By default,ox_profilestarts out paused
so that it will not incur any overhead for your app. Go to the/ox_profile/unpauseroute to unpause and begin profiling so that/ox_profile/statusshows something interesting.OutputCurrentlyox_profileis in alpha mode and so the output is fairly
bare bones. When you look at the results of calling thequerymethod
of an instance ofSimpleLauncherin stand alone mode or at the/ox_profile/statusroute when running with flask, what you get is a
raw list of each function your program has called along with how many
times that function was called in our sampling.DesignHigh Level DesignPython offers a number of ways to get profiling information. In addition
to high-level profiling tools such as in theprofilepackage, there
are specialized functions likesys.settraceandsys.setprofile.
These are used for deterministic profiling and relatively robust but
have some overhead as they are invoked on each function call.At a high level, we want a way to get a sample of what the python
interpreter is doing at any give instance. The sampling approach has the
advantage that by turning the sampling interval low enough, we can add
arbitrarily low overhead and make profiling feasible in a production
system. By taking a long enough sample, however, we should be able to
get arbitrarily accurate profiling information.Low Level DesignAt a low level, we do this sampling usingsys._current_frames. As
suggested by the leading underscore, this system function may be a bit
less robust. Indeed, the documentation says “This function should be
used for specialized purposes only.” Hopefully the core python
developers will not make major changes to such a useful function.In any case, the most interesting class is theSamplerclass in theox_profile.core.samplingmodule. This class has a run method which
does the following:Usessys.setswitchintervalto try and prevent a thread context
switch.Callssys._current_framesto sample what the python interpreter
is doing.Updates a simple in-memory database of what functions are running.In principle, you could just use the Sampler via something like>>> from ox_profile.core import sampling, recording
>>> sampler = sampling.Sampler(recording.CountingRecorder())
>>> def foo():
...     sampler.run()
...     return 'done'
...
>>> foo()The above would have the sampler take a snapshot of the stack frames
when thefoofunction is run. Of course, this isn’t very useful by
itself because it just tells you thatfoois being run. It could be
useful if there were other threads which were running because the
sampler would tell you what stack frame those threads were in.In principle, you could just call theSampler.runmethod to track
other threads but that still isn’t very convenient. To make things easy
to use, we provide theSimpleLauncherclass in theox_profile.core.launchersmodule as shown in the Usage section. TheSimpleLauncherbasically does the following:Creates an instance of theSamplerclass with reasonable
defaults.Initializes itself as a daemon thread and starts.Pauses itself so the thread does nothing so as to not load the
system.Provides anunpausemethod you can use when you want to turn on
profiling.Provides apausemethod if you want to turn off profiling.In principle, you don’t need much beyond theSamplerbut theSimpleLaunchermakes it easier to launch aSamplerin a separate
thread.Known IssuesGranularityWith statistical profiling, we need to ask the thread to sleep for some
small amount so that it does not overuse CPU resources. Sadly, the
minimum sleep time (using eithertime.sleeporwaiton a thread
event) is on the order of 1–10 milliseconds on most operating systems.
This means that you can not efficiently do statistical profiling at a
granularity finer than about 1 millisecond.Thus you should consider statistical profiling as a tool to find the
relatively slow issues in production and not a tool for optimizing
issues faster than about a millisecond.