[Git][security-tracker-team/security-tracker][master] 8 commits: bugs.py: BugFromDB: speed up list construction

Emilio Pozuelo Monfort (@pochu) pochu at debian.org
Mon Feb 16 08:59:40 GMT 2026



Emilio Pozuelo Monfort pushed to branch master at Debian Security Tracker / security-tracker


Commits:
87bb8cd3 by Helmut Grohne at 2026-02-15T13:32:47+01:00
bugs.py: BugFromDB: speed up list construction

When there are many NOTE entries for a bug, loading them can impact
performance. Optimize the loop using list.extend.

- - - - -
df2de9cf by Helmut Grohne at 2026-02-15T13:32:47+01:00
bugs.py: BugFromDB: inline private lookup functions

They used to be called several times until 20 years ago. Now that there
is only one invocation each, inline them.

- - - - -
f1af6f25 by Helmut Grohne at 2026-02-15T13:32:48+01:00
web_support.py: speed up escapeHTML*

- - - - -
06bb374a by Helmut Grohne at 2026-02-15T13:32:48+01:00
web_support.py: remove unnecessary branch in Tag.__init__

This is a hot code path in the security tracker. removeprefix is
available since Python 3.9 (bullseye).

- - - - -
005c817e by Helmut Grohne at 2026-02-15T13:32:48+01:00
web_support.py: improve Python 3 compatibility

sys.log was available in Python 2.x only.

- - - - -
1d4767ca by Helmut Grohne at 2026-02-15T13:32:48+01:00
web_support.py: drop Tag wrapper functions tag and emptyTag

While emptyTag has no uses left, the primary purpose of the tag wrapper
seems to have been turning keyword arguments into a passed dict. A
notable use here is allowing to pass attributes named "name" or
"contents". In Python versions >= 3.8 (bullseye), this can be achieved
using position-only arguments. We may now call Tag("input", name="spam")
and have the keyword passed via attribs. The wrapper was called a lot,
so we save a lot of unnecessary function calls.

- - - - -
d3e2982b by Helmut Grohne at 2026-02-15T13:32:48+01:00
web_support.py: fix wrong variable in error message

- - - - -
4dd115de by Emilio Pozuelo Monfort at 2026-02-16T08:59:35+00:00
Merge branch 'performance1' into 'master'

Improve web tracker performance

See merge request security-tracker-team/security-tracker!267
- - - - -


3 changed files:

- bin/tracker_service.py
- lib/python/bugs.py
- lib/python/web_support.py


Changes:

=====================================
bin/tracker_service.py
=====================================
@@ -71,8 +71,8 @@ class BugFilter:
             else:
                 l.append(LABEL(INPUT(desc, type='checkbox', name='filter', value=prop, onChange='this.form.submit()'), rel=field))
 
-        return FORM(tag("SPAN",l, id="filters"),
-                    tag("NOSCRIPT", [INPUT(type='submit', value='Apply')]),
+        return FORM(Tag("SPAN",l, id="filters"),
+                    Tag("NOSCRIPT", [INPUT(type='submit', value='Apply')]),
                     method='get')
 
     def urgencyFiltered(self, urg, vuln):
@@ -1532,7 +1532,7 @@ Debian bug number.'''),
         return SPAN(contents, _class="green")
 
     def make_mouseover(self, contents, text):
-        return tag("SPAN", contents, title=text)
+        return Tag("SPAN", contents, title=text)
 
     def make_dangerous(self, contents):
         return SPAN(contents, _class="dangerous")


=====================================
lib/python/bugs.py
=====================================
@@ -303,29 +303,19 @@ class BugFromDB(Bug):
     def __init__(self, cursor, name):
         assert isinstance(name, str)
 
-        def lookup(bug):
-            for r in cursor.execute('SELECT * FROM bugs WHERE name = ?',
-                                    (bug,)):
-                return r
-            else:
-                return None
-
-        def lookup_dsa(bug):
-            for r in cursor.execute(
-                """SELECT * FROM bugs
-                WHERE name = ? OR name LIKE (? || '-%')
-                ORDER BY release_date DESC
-                LIMIT 1""", (bug, bug,)):
-                return r
-            else:
-                return None
-
-        r = lookup(name)
+        r = cursor.execute('SELECT * FROM bugs WHERE name = ?',
+                           (name,)).fetchone()
         if r is None:
             name_components = name.split('-')
             name_source = name_components[0]
             if name_source == 'DSA' and 2 <= len(name_components) <= 3:
-                r = lookup_dsa('DSA-' + name_components[1])
+                r = cursor.execute(
+                    """SELECT * FROM bugs
+                    WHERE name = ? OR name LIKE ?
+                    ORDER BY release_date DESC
+                    LIMIT 1""",
+                    ('DSA-' + name_components[1],
+                     'DSA-' + name_components[1] + '-%',)).fetchone()
             if r is None:
                 raise ValueError("unknown bug " + repr(name))
 
@@ -342,12 +332,11 @@ class BugFromDB(Bug):
         for (x,) in cursor.execute\
             ('SELECT target FROM bugs_xref WHERE source = ?', (name,)):
             self.xref.append(x)
-        for (t, c) in cursor.execute\
+        self.comments.extend(cursor.execute\
             ("""SELECT typ, comment FROM bugs_notes
             WHERE bug_name = ?
             ORDER BY rowid""",
-             (name,)):
-            self.comments.append((t, c))
+             (name,)))
 
         # temporary list required because loadBugs needs the cursor
         for (nid, package, fixed_version, release, urgency, package_kind,


=====================================
lib/python/web_support.py
=====================================
@@ -94,9 +94,9 @@ class Service:
                 (magic, version, cli_size, cli_count, env_size, env_count) = \
                         struct.unpack("!6I", header)
                 if magic != 0x15fd34df:
-                    sys.log("unknown magic number %08X", magic)
+                    self.log("unknown magic number %08X", magic)
                 if version != 1:
-                    sys.log("unknown version %08X", magic)
+                    self.log("unknown version %08X", version)
                 cli = read(cli_size).split('\0')[:-1]
                 env = {}
                 for x in read(env_size).split('\0')[:-1]:
@@ -221,24 +221,24 @@ class URLFactory:
         self.updateParamsDict(args)
 
 charToHTML = {
-    '<' : '<',
-    '>' : '>',
-    '&' : '&',
+    ord('<') : '<',
+    ord('>') : '>',
+    ord('&') : '&',
 }
 charToHTMLattr = {
-    '&' : '&',
-    '"' : '&34;',
+    ord('&') : '&',
+    ord('"') : '&34;',
 }
 
 def escapeHTML(str):
     '''Replaces the characters <>& in the passed strings with their
     HTML entities.'''
-    return ''.join([charToHTML.get(ch, ch) for ch in str])
+    return str.translate(charToHTML)
 
 def escapeHTMLattr(str):
     '''Replaces the characters &" in the passed strings with their
     HTML entities.'''
-    return ''.join([charToHTMLattr.get(ch, ch) for ch in str])
+    return str.translate(charToHTMLattr)
 
 class HTMLBase:
     def flatten(self, write):
@@ -291,7 +291,14 @@ class Tag(HTMLBase):
 
     re_name = re.compile(r'\A_?[a-zA-Z][a-zA-Z0-9]*\Z')
 
-    def __init__(self, name, contents, attribs={}):
+    def __init__(self, name, contents=None, /, **attribs):
+        """Creates a new tag object.
+
+        name - name of the tag
+        contents - an optional sequence object (or iterator) for the enclosed
+                   contents
+        attribs - keyword arguments forming attributes
+        """
         self._check(name)
         self.__name = name
         attrs = []
@@ -301,10 +308,7 @@ class Tag(HTMLBase):
                 continue
             self._check(key)
             append(' ')
-            if key[0] == '_':
-                append(key[1:])
-            else:
-                append(key)
+            append(key.removeprefix('_'))
             append('="')
             append(escapeHTMLattr(str(value)))
             append('"')
@@ -349,85 +353,68 @@ class Tag(HTMLBase):
         self.flatten(r.write)
         return r.getvalue()
 
-def tag(__name, __contents, **__attribs):
-    """Creates a new tag object.
-
-    name - name of the tag
-    contents - a sequence objet (or iterator) for the enclosed contents
-    attribs - keyword arguments forming attributes
-    """
-    return Tag(__name, __contents, __attribs)
-
-def emptyTag(__name, **__attribs):
-    """A tag without contents.
-
-    name - name of the tag
-    attribs - keyword arguments forming attributes
-    """
-    return Tag(__name, None, __attribs)
-
 def A(url, text=None):
     if text is None:
         text = url
-    return tag('a', text, href=str(url))
+    return Tag('a', text, href=str(url))
 def STYLE(contents, type='text/css'):
-    return tag('style', contents, type=type)
+    return Tag('style', contents, type=type)
 def SCRIPT(contents, type="text/javascript", src=""):
-    return tag('script', contents, type=type, src=src)
+    return Tag('script', contents, type=type, src=src)
 def LINK(contents, type="text/css", rel="stylesheet", href=""):
-    return tag('link', contents, type=type, rel=rel, href=href)
+    return Tag('link', contents, type=type, rel=rel, href=href)
 def TITLE(contents):
-    return tag('title', contents)
+    return Tag('title', contents)
 def HTML(head, body):
-    return tag('html', (HEAD(head), BODY(body)))
+    return Tag('html', (HEAD(head), BODY(body)))
 def HEAD(contents):
-    return tag('head', contents)
+    return Tag('head', contents)
 def BODY(contents, onload=None):
-    return tag('body', contents, onload=onload)
+    return Tag('body', contents, onload=onload)
 def H1(contents):
-    return tag('h1', contents)
+    return Tag('h1', contents)
 def H2(contents):
-    return tag('h2', contents)
+    return Tag('h2', contents)
 def H3(contents):
-    return tag('h3', contents)
+    return Tag('h3', contents)
 def P(*contents):
     return Tag('p', contents)
 def SPAN(*__contents, **__attribs):
-    return Tag('span', __contents, __attribs)
+    return Tag('span', __contents, **__attribs)
 def HR():
-    return tag('hr', ())
+    return Tag('hr')
 def BR():
-    return tag('br', ())
+    return Tag('br')
 def CODE(*contents):
-    return tag('code', contents)
+    return Tag('code', contents)
 def EM(*contents):
-    return tag('em', contents)
+    return Tag('em', contents)
 def B(contents):
-    return tag('b', contents)
+    return Tag('b', contents)
 def TABLE(contents):
-    return tag('table', contents)
+    return Tag('table', contents)
 def TR(*contents):
-    return tag('tr', contents)
+    return Tag('tr', contents)
 def TH(*contents):
-    return tag('th', contents)
+    return Tag('th', contents)
 def TD(*contents):
-    return tag('td', contents)
+    return Tag('td', contents)
 def FORM(*__contents, **__attribs):
-    return Tag('form', __contents, __attribs)
+    return Tag('form', __contents, **__attribs)
 def LABEL(*__contents, **__attribs):
-    return Tag('label', __contents, __attribs)
+    return Tag('label', __contents, **__attribs)
 def INPUT(*__contents, **__attribs):
-    return Tag('input', __contents, __attribs)
+    return Tag('input', __contents, **__attribs)
 def UL(contents):
-    return tag('ul', contents)
+    return Tag('ul', contents)
 def LI(*__contents, **__attribs):
-    return Tag('li', __contents, __attribs)
+    return Tag('li', __contents, **__attribs)
 def HEADER(*__contents, **__attribs):
-    return Tag('header', __contents, __attribs)
+    return Tag('header', __contents, **__attribs)
 def FOOTER(*__contents, **__attribs):
-    return Tag('footer', __contents, __attribs)
+    return Tag('footer', __contents, **__attribs)
 def NAV(*__contents, **__attribs):
-    return Tag('nav', __contents, __attribs)
+    return Tag('nav', __contents, **__attribs)
 
 def _linkify(match):
     extra = match.group(2)
@@ -476,9 +463,9 @@ def make_pre(lines):
                 else:
                     append(group)
         else:
-            append(tag("SPAN",line))
+            append(Tag("SPAN", line))
         append(BR())
-    return tag('pre', pre)
+    return Tag('pre', pre)
 
 def make_menu(convert, *entries):
     """Creates an unnumbered list of hyperlinks.
@@ -497,7 +484,7 @@ def make_menu(convert, *entries):
             append(LI(A(convert(relurl), label)))
         else:
             append(LI(e))
-    return tag('ul', ul)
+    return Tag('ul', ul)
 
 def make_numbered_list(entries):
     """Creates a numbered list.
@@ -506,7 +493,7 @@ def make_numbered_list(entries):
     append = ol.append
     for e in entries:
         append(LI(e))
-    return tag('ol', ol)
+    return Tag('ol', ol)
 
 def make_list(lst, separator=", "):
     """Creates a list of HTML elements."""
@@ -736,8 +723,8 @@ class WebServiceBase:
             body_list = list(body)
         body_list[:0] = (HEADER(H1(title)),)
 
-        return tag('html',
-                   (HEAD(head_list), Tag('body', body_list, body_attribs)))
+        return Tag('html',
+                   (HEAD(head_list), Tag('body', body_list, **body_attribs)))
 
     def pre_dispatch(self, url):
         """Invoked by handle prior to calling the registered handler."""



View it on GitLab: https://salsa.debian.org/security-tracker-team/security-tracker/-/compare/030d1a1b68cff20299023b18f551a41601e75e5c...4dd115de0f0bf5ff9b30e35630c4769d47df045e

-- 
View it on GitLab: https://salsa.debian.org/security-tracker-team/security-tracker/-/compare/030d1a1b68cff20299023b18f551a41601e75e5c...4dd115de0f0bf5ff9b30e35630c4769d47df045e
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/debian-security-tracker-commits/attachments/20260216/979f98fd/attachment-0001.htm>


More information about the debian-security-tracker-commits mailing list