Skip to content

apt_info: massive optimizations and rewrite - #269

Open
anarcat wants to merge 12 commits into
prometheus-community:masterfrom
anarcat:optimizations
Open

anarcat wants to merge 12 commits into
prometheus-community:masterfrom
anarcat:optimizations

Conversation

@anarcat

@anarcat anarcat commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

This is a set of tweaks to the apt_info.py script to optimize collection and reorganize the codebase. It started with an attempt at optimizing the code and ultimately meant essentially rewriting the whole thing since it required refactoring the functions into a single one to optimize data structures.

The performance improvement on my laptop is kind of dramatic:

> hyperfine 'python apt_info-34f9073d8.py' 'python apt_info-69fe18add.py' 'python apt_info.py'
Benchmark 1: python apt_info-34f9073d8.py
  Time (mean ± σ):     10.081 s ±  0.318 s    [User: 5.796 s, System: 4.283 s]
  Range (min … max):    9.575 s … 10.832 s    10 runs
 
Benchmark 2: python apt_info-69fe18add.py
  Time (mean ± σ):      9.517 s ±  0.280 s    [User: 5.380 s, System: 4.136 s]
  Range (min … max):    9.101 s …  9.864 s    10 runs
 
Benchmark 3: python apt_info.py
  Time (mean ± σ):     780.7 ms ±  37.3 ms    [User: 741.2 ms, System: 39.3 ms]
  Range (min … max):   732.1 ms … 843.9 ms    10 runs
 
Summary
  python apt_info.py ran
   12.19 ± 0.68 times faster than python apt_info-69fe18add.py
   12.91 ± 0.74 times faster than python apt_info-34f9073d8.py

That's 10 seconds to 800ms!!

On a canary server of ours -- a minimal example -- the performance is less impressive, going from 1.6s to 500ms... But still a 3x improvement!

(Note that the filename above include commit hashes that do not exist here, but are from our own monorepo which includes this file, that we're trying to get back in sync with upstream.)


This a large number of commits that have accumulated on our end in the past 18 months. We have held from contributing those back because !234 got stuck, but seeing that it's now merged gives us hope that we can contribute again.

I'm really sorry about the state of this PR. I wish things were more orderly, but this is the best I could do with a couple of hours of work.

You'll note that the commit starts with a revert of dc297f1, which changed the metric names, because our patches were made without merging that change from !234 that I had completely overlooked. Reverting the patch, applying our patchset, and reapplying the fix was much easier than constantly resolving the conflicts over the rebase. If that's really a problem for the history, I could try rebasing the entire thing without commits, but given the massive rewrite, it's going to be a huge ordeal and I'm not sure I would go through it. I'll also note that

We still have a small delta after this. I've ran black over the file locally, and would love to do so here, but for the sake of readability, I have actually managed to port the patches without black. I'd love to hear if such a noop PR would be accepted as well, following this one, to remove our last delta.

Thanks!

@anarcat

anarcat commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

We still have a small delta after this. I've ran black over the file locally, and would love to do so here, but for the sake of readability, I have actually managed to port the patches without black. I'd love to hear if such a noop PR would be accepted as well, following this one, to remove our last delta.

... and it turns out flake8 is unhappy here so, as a stopgap, i just made a commit to please it, but it's exactly the kind of stuff black completely fixes without us having to think about it. :)

anarcat and others added 12 commits July 22, 2026 13:37
This will make it easier to port my patches.

This reverts commit a6e40c8.

Signed-off-by: Antoine Beaupré <anarcat@debian.org>
The phasing_applied attribute does not exist in the version of the apt
python lib in debian bookworm and it causes the script to crash when
there are packages that have pending upgrades.

Signed-off-by: Antoine Beaupré <anarcat@debian.org>
This should help us find broken packages (tpo/tpa/team#42542).

One has to wonder whether it's wise to add yet another one of those
loops/functions, but I have actually *tried* to refactor this in a
single loop and increment the metrics instead, and it's actually
slower.

It *might* still be possible to refactor to create lists and then
post-process them all at once, but it would make the code harder to
read as well.

Signed-off-by: Antoine Beaupré <anarcat@debian.org>
This is a small optimization, but it does improve things a little bit:

> hyperfine 'python apt_info-34f9073d8.py' 'python apt_info-c3be3d0ed.py' 'python apt_info.py'
Benchmark 1: python apt_info-34f9073d8.py
  Time (mean ± σ):      9.908 s ±  0.368 s    [User: 5.704 s, System: 4.203 s]
  Range (min … max):    9.232 s … 10.444 s    10 runs

Benchmark 2: python apt_info-c3be3d0ed.py
  Time (mean ± σ):     10.207 s ±  0.386 s    [User: 5.941 s, System: 4.264 s]
  Range (min … max):    9.566 s … 10.798 s    10 runs

Benchmark 3: python apt_info.py
  Time (mean ± σ):     10.048 s ±  0.254 s    [User: 5.766 s, System: 4.280 s]
  Range (min … max):    9.660 s … 10.362 s    10 runs

Summary
  python apt_info-34f9073d8.py ran
    1.01 ± 0.05 times faster than python apt_info.py
    1.03 ± 0.05 times faster than python apt_info-c3be3d0ed.py

Above, the first run is before the new stats, then the new stats make
that slower, but then we bring it almost back to normal.

Signed-off-by: Antoine Beaupré <anarcat@debian.org>
This trims off about 30% run time on my computer, which has thousands
of installed packages. This is less significant on smaller installs,
but here apt_info takes a solid 10 seconds to run fully, and 5 seconds
of that is spent in that funciton, mostly loading the origins,
according to a cProfile.

Hyperfine thinks this takes the runtime from 9-10s down to 6-7s:

Benchmark 1: python apt_info-base.py
  Time (mean ± σ):      9.954 s ±  0.262 s    [User: 5.955 s, System: 3.999 s]
  Range (min … max):    9.581 s … 10.323 s    10 runs

Benchmark 2: python apt_info.py
  Time (mean ± σ):      7.520 s ±  0.470 s    [User: 4.760 s, System: 2.760 s]
  Range (min … max):    6.792 s …  8.118 s    10 runs

Summary
  python apt_info.py ran
    1.32 ± 0.09 times faster than python apt_info-base.py

Signed-off-by: Antoine Beaupré <anarcat@debian.org>
Signed-off-by: Antoine Beaupré <anarcat@debian.org>
This is a rather disappointing 50% performance improvement,
unfortunately:

Benchmark 1: python apt_info-a0e95ee50.py
  Time (mean ± σ):      6.638 s ±  0.208 s    [User: 3.949 s, System: 2.688 s]
  Range (min … max):    6.259 s …  6.990 s    10 runs

Benchmark 2: python apt_info.py
  Time (mean ± σ):      6.158 s ±  0.237 s    [User: 3.538 s, System: 2.619 s]
  Range (min … max):    5.951 s …  6.566 s    10 runs

Summary
  python apt_info.py ran
    1.08 ± 0.05 times faster than python apt_info-a0e95ee50.py

It is still faster and, I find, more readable.

Signed-off-by: Antoine Beaupré <anarcat@debian.org>
That constructor does the following call:

    indexfile = pkg._pcache._list.find_index(packagefile)

... to check if the `indexfile` is "trusted". We don't really care
about this: we don't check for that value and essentially discard
it. But it's *really* expensive. Here, on my laptop, is_obsolete is
called nearly 6000 times, which leads to 12k calls to find_index,
totaling 2.6s.

The performance improvements are dramatic, probably even more than the
fact that it required me to essentially rewrite the entire file to get
to that point. Here is what hyperfine thinks:

> hyperfine 'python apt_info-34f9073d8.py' 'python apt_info-ebcb9406d.py' 'python apt_info.py'
Benchmark 1: python apt_info-34f9073d8.py
  Time (mean ± σ):      9.567 s ±  0.263 s    [User: 5.432 s, System: 4.134 s]
  Range (min … max):    9.147 s …  9.932 s    10 runs

Benchmark 2: python apt_info-ebcb9406d.py
  Time (mean ± σ):      6.611 s ±  0.276 s    [User: 3.769 s, System: 2.841 s]
  Range (min … max):    6.257 s …  7.137 s    10 runs

Benchmark 3: python apt_info.py
  Time (mean ± σ):     821.6 ms ±  21.1 ms    [User: 776.6 ms, System: 44.7 ms]
  Range (min … max):   779.1 ms … 862.0 ms    10 runs

Summary
  python apt_info.py ran
    8.05 ± 0.39 times faster than python apt_info-ebcb9406d.py
   11.64 ± 0.44 times faster than python apt_info-34f9073d8.py

This is almost an order of magnitude faster!

Signed-off-by: Antoine Beaupré <anarcat@debian.org>
The duplicate was done to optimize the hot loop, but now it just looks
strange to have them both.

Signed-off-by: Antoine Beaupré <anarcat@debian.org>
Signed-off-by: Antoine Beaupré <anarcat@debian.org>
Fix metric names to follow best practices.

Co-authored-by: Ben Kochie <superq@gmail.com>
Signed-off-by: Ben Kochie <superq@gmail.com>
Signed-off-by: Antoine Beaupré <anarcat@debian.org>
Signed-off-by: Antoine Beaupré <anarcat@debian.org>
@anarcat

anarcat commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

rerolled with signoffs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants