Patchwork [6,of,6] hgweb: profile HTTP requests

login
register
mail settings
Submitter Gregory Szorc
Date Aug. 15, 2016, 12:03 a.m.
Message ID <817b24db6bea900c690d.1471219438@ubuntu-vm-main>
Download mbox | patch
Permalink /patch/16294/
State Superseded
Headers show

Comments

Gregory Szorc - Aug. 15, 2016, 12:03 a.m.
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1471219358 25200
#      Sun Aug 14 17:02:38 2016 -0700
# Node ID 817b24db6bea900c690dae7ab0f2d15244d78724
# Parent  a04a42c139225b2f5b3166541fd7466190459822
hgweb: profile HTTP requests

Currently, running `hg serve --profile` doesn't yield anything useful:
when the process is terminated the profiling output displays results
from the main thread, which typically spends most of its time in
select.select(). Furthermore, it has no meaningful results from
mercurial.* modules because the threads serving HTTP requests don't
actually get profiled.

This patch teaches the hgweb wsgi applications to profile individual
requests. If profiling is enabled, the profiler kicks in after
HTTP/WSGI environment processing but before Mercurial's main request
processing.

The profile results are printed to the configured profiling output.
If running `hg serve` from a shell, they will be printed to stderr,
just before the HTTP request line is logged. If profiling to a file,
we only write a single profile to the file because the file is not
opened in append mode. We could add support for appending to files
in a future patch if someone wants it.

Having this profiling data has already given me new avenues of
performance optimization to pursue in the HTTP server.

Patch

diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -23,16 +23,17 @@  from .common import (
 )
 from .request import wsgirequest
 
 from .. import (
     encoding,
     error,
     hg,
     hook,
+    profiling,
     repoview,
     templatefilters,
     templater,
     ui as uimod,
     util,
 )
 
 from . import (
@@ -300,18 +301,19 @@  class hgweb(object):
 
     def run_wsgi(self, req):
         """Internal method to run the WSGI application.
 
         This is typically only called by Mercurial. External consumers
         should be using instances of this class as the WSGI application.
         """
         with self._obtainrepo() as repo:
-            for r in self._runwsgi(req, repo):
-                yield r
+            with profiling.maybeprofile(repo.ui):
+                for r in self._runwsgi(req, repo):
+                    yield r
 
     def _runwsgi(self, req, repo):
         rctx = requestcontext(self, repo)
 
         # This state is global across all threads.
         encoding.encoding = rctx.config('web', 'encoding', encoding.encoding)
         rctx.repo.ui.environ = req.env
 
diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py
--- a/mercurial/hgweb/hgwebdir_mod.py
+++ b/mercurial/hgweb/hgwebdir_mod.py
@@ -26,16 +26,17 @@  from .common import (
     staticfile,
 )
 from .request import wsgirequest
 
 from .. import (
     encoding,
     error,
     hg,
+    profiling,
     scmutil,
     templater,
     ui as uimod,
     util,
 )
 
 from . import (
     hgweb_mod,
@@ -212,17 +213,19 @@  class hgwebdir(object):
         allow_read = ui.configlist('web', 'allow_read', untrusted=True)
         # by default, allow reading if no allow_read option has been set
         if (not allow_read) or ismember(ui, user, allow_read):
             return True
 
         return False
 
     def run_wsgi(self, req):
-        return self._runwsgi(req)
+        with profiling.maybeprofile(self.ui):
+            for r in self._runwsgi(req):
+                yield r
 
     def _runwsgi(self, req):
         try:
             self.refresh()
 
             virtual = req.env.get("PATH_INFO", "").strip('/')
             tmpl = self.templater(req)
             ctype = tmpl('mimetype', encoding=encoding.encoding)
diff --git a/tests/test-profile.t b/tests/test-profile.t
--- a/tests/test-profile.t
+++ b/tests/test-profile.t
@@ -26,9 +26,23 @@  test --profile
   $ hg --profile st 2>../out
   $ grep 'events: Ticks' ../out > /dev/null || cat ../out
 
   $ hg --profile --config profiling.output=../out st
   $ grep 'events: Ticks' ../out > /dev/null || cat ../out
 
 #endif
 
+#if lsprof serve
+
+Profiling of HTTP requests works
+
+  $ hg --profile --config profiling.format=text --config profiling.output=../profile.log serve -d -p $HGPORT --pid-file ../hg.pid -A ../access.log
+  $ cat ../hg.pid >> $DAEMON_PIDS
+  $ hg -q clone -U http://localhost:$HGPORT ../clone
+
+A single profile is logged because file logging doesn't append
+  $ grep CallCount ../profile.log | wc -l
+  1
+
+#endif
+
   $ cd ..