Skip to content

Add functionality to graph DHCP stats on each VLAN page and Prefix page in NAV #3633

Merged
lunkwill42 merged 4 commits intoUninett:masterfrom
jorund1:dhcpstats-frontend
Feb 12, 2026
Merged

Add functionality to graph DHCP stats on each VLAN page and Prefix page in NAV #3633
lunkwill42 merged 4 commits intoUninett:masterfrom
jorund1:dhcpstats-frontend

Conversation

@jorund1
Copy link
Copy Markdown
Collaborator

@jorund1 jorund1 commented Oct 28, 2025

Scope and purpose

Fixes #2373. Another PR intends to add documentation and release notes.

This pull request

  • Adds DHCP stats graphs to VLAN and Prefix pages in NAV where recent-enough stats for that VLAN/Prefix are found in the Carbon/Graphite database:
    dhcp-frontend-delimited-timeseries-showcase

Contributor Checklist

Every pull request should have this checklist filled out, no matter how small it is.
More information about contributing to NAV can be found in the
Hacker's guide to NAV.

  • Added a changelog fragment for towncrier
  • Added/amended tests for new/changed code
  • Added/changed documentation see Add documentation for DHCP stats #3785
  • Linted/formatted the code with ruff, easiest by using pre-commit
  • Wrote the commit message so that the first line continues the sentence "If applied, this commit will ...", starts with a capital letter, does not end with punctuation and is 50 characters or less long. See https://cbea.ms/git-commit/
  • Based this pull request on the correct upstream branch: For a patch/bugfix affecting the latest stable version, it should be based on that version's branch (<major>.<minor>.x). For a new feature or other additions, it should be based on master.
  • If applicable: Created new issues if this PR does not fix the issue completely/there is further work to be done TODO: add a new issue for using Rickshaw graphs instead of PNG images in graph navlets
  • If it's not obvious from a linked issue, describe how to interact with NAV in order for a reviewer to observe the effects of this change first-hand (commands, URLs, UI interactions) See screencast below
  • If this results in changes in the UI: Added screenshots of the before and after
  • If this adds a new Python source code file: Added the boilerplate header to that file

Testing It For Yourself

You can use the script below to fill the prefixes 192.0.0.0/24, 192.0.1.0/24, 192.0.0.0/26, and ::2:0:0/96 with DHCP stats (you might need to install socat and gawk, and you might need to change 127.0.0.1 to the address of your Carbon/Graphite database):

#!/bin/sh

cat <<EOF |
4 example-1 admin 192.0.0.1 192.0.0.127 127 127 0 100000
4 example-1 admin 192.0.0.129 192.0.0.255 127 127 0 100000
4 example-1 admin 192.0.1.1 192.0.1.255 255 255 0 100000

4 example-1 guest 192.0.2.0 192.0.2.255 256 256 0 10000
4 example-1 guest 192.0.2.0 192.0.2.127 128 128 10001 50000
4 example-1 guest 192.0.2.128 192.0.2.191 64 64 10001 50000
4 example-1 guest 192.0.2.192 192.0.2.255 64 64 10001 50000

6 example-1 admin 0:0:0:0:0:2:0:0 0:0:0:0:0:2:ffff:ffff 4294967296 100 0 100000
6 example-1 guest 0:0:0:0:0:2:0:0 0:0:0:0:0:2:ffff:ffff 4294967296 100 0 100000
EOF

gawk '
BEGIN {
  NOW = systime()
  srand(11)
  INTERVAL = 300
  DAYS = 30
}

NF == 9 {
  a = rand()
  b = rand()
  c = rand()
  _ = 1 / (a + b + c)
  a = a * _
  b = b * _
  c = c * _
  d = rand()*3.14*2
  e = 20 + rand()*8
  f = rand()

  ip_version = $1
  server_name = $2
  group_name = $3
  first_ip = $4; gsub(/[.:]/, "_", first_ip)
  last_ip = $5; gsub(/[.:]/, "_", last_ip)
  total_ips = $6
  upper_limit = $7
  start_offset = $8 * INTERVAL
  end_offset = $9 * INTERVAL
  noise = rand()
  for (offset = start_offset; offset < end_offset; offset += INTERVAL) {
    sine = (sin((offset*3.14*2)/(3600*e) + d)+1)/2
    noise = noise*f + rand()*(1-f)
    assigned_ips = int(sine*upper_limit*a+upper_limit*b+noise*upper_limit*c)
    printf("nav.dhcp.servers.%s.range.custom_groups.%s.%d.%s.%s.unassigned %d %d\n", server_name, group_name, ip_version, first_ip, last_ip, total_ips - assigned_ips, NOW - offset)
    printf("nav.dhcp.servers.%s.range.custom_groups.%s.%d.%s.%s.assigned %d %d\n", server_name, group_name, ip_version, first_ip, last_ip, assigned_ips, NOW - offset)
    printf("nav.dhcp.servers.%s.range.custom_groups.%s.%d.%s.%s.total %d %d\n", server_name, group_name, ip_version, first_ip, last_ip, total_ips, NOW - offset)
  }
}' |

socat - tcp:127.0.0.1:2003

Here's a screencast (which, after the most recent changes, is a bit outdated, but still representative):

screencast.mp4

At 2:00 in the video, notice that the graph labelled DHCP ranges in 'guest' on server 'kea-trd' has stats from ranges in both 192.0.2.0/24 and 198.51.100.0/24 despite vlan 20 only containing 192.0.2.0/24. This is because whenever a group (here: 'guest' on server 'kea-trd') has at least one range/pool/subnet overlapping with the VLAN/Prefix, the whole group will be displayed on that VLAN/Prefix's page.

@jorund1 jorund1 force-pushed the dhcpstats-frontend branch from 3439a9d to 90682bc Compare October 28, 2025 17:30
@codecov
Copy link
Copy Markdown

codecov bot commented Oct 28, 2025

Codecov Report

❌ Patch coverage is 68.96552% with 36 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.32%. Comparing base (9b67897) to head (9518a66).
⚠️ Report is 5 commits behind head on master.

Files with missing lines Patch % Lines
python/nav/dhcpstats/common.py 64.28% 25 Missing ⚠️
python/nav/metrics/graphs.py 30.76% 9 Missing ⚠️
python/nav/web/info/prefix/views.py 88.88% 1 Missing ⚠️
python/nav/web/info/vlan/views.py 85.71% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3633      +/-   ##
==========================================
+ Coverage   63.31%   63.32%   +0.01%     
==========================================
  Files         618      618              
  Lines       45761    45873     +112     
  Branches       43       43              
==========================================
+ Hits        28973    29051      +78     
- Misses      16778    16812      +34     
  Partials       10       10              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jorund1 jorund1 force-pushed the dhcpstats-frontend branch 2 times, most recently from db85e5a to 50cb13a Compare October 28, 2025 18:29
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Copy link
Copy Markdown
Member

@lunkwill42 lunkwill42 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall the code seems nicely structured. After a long F2F session, I would say my most important suggestion is to split this into two PRs: One for the rewrite of the backend functionality, and one for the purely front-end related additions.

Lastly, I understand there is also a documentation PR coming, which is nice.

@lunkwill42
Copy link
Copy Markdown
Member

Overall the code seems nicely structured. After a long F2F session, I would say my most important suggestion is to split this into two PRs: One for the rewrite of the backend functionality, and one for the purely front-end related additions.

Lastly, I understand there is also a documentation PR coming, which is nice.

Also a reminder to properly enable the pre-commit hooks in each clone you have: Linting failures abound here...

@lunkwill42
Copy link
Copy Markdown
Member

Re: Tip about splitting into multiple PRs: I cannot recall whether you have permission to push branches on the upstream repo, but if you do, it makes it easier to build PRs that depend on each other. The first PR would have master as its base. The second PR would have the first PR's branch as its base. This makes the diffs more manageable for review.

@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@jorund1 jorund1 marked this pull request as draft January 26, 2026 06:02
@jorund1 jorund1 force-pushed the dhcpstats-frontend branch from 00ee9a5 to 29bc3fb Compare February 6, 2026 09:31
@jorund1
Copy link
Copy Markdown
Collaborator Author

jorund1 commented Feb 6, 2026

Rebasing on top of #3766..

@jorund1 jorund1 force-pushed the dhcpstats-frontend branch from 29bc3fb to 2aac743 Compare February 6, 2026 13:22
@jorund1 jorund1 changed the base branch from master to dhcpstats-backend February 6, 2026 13:24
@jorund1 jorund1 requested a review from lunkwill42 February 6, 2026 13:29
@jorund1 jorund1 marked this pull request as ready for review February 6, 2026 13:33
Copy link
Copy Markdown
Member

@lunkwill42 lunkwill42 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, let's go with this!

I have an inline concern that should be followed up with a new issue or PR.

There's also this non-blocking follow-up item: If Graphite is unavailable, I get a very useful red "Graphite unavailable" message in the GUI. However, the backend request fails with an unhandled 500 message, which ultimately gets sent as an e-mail to the site admin. These should really be handled, there is no reason to bother the site admin with lots of tracebacks for an issue that ultimately is not a problem in NAV but a problem with how Graphite is operating (which should be monitored in other ways)

These can be used instead of string interpolations which quickly become
unwieldy when you want a little bit more advanced Graphite renders.
The function fetch_graph_urls_for_prefixes takes a set of prefixes
(e.g. obtained from a models.manage.Prefix or from a models.manage.Vlan) and
returns one url per DHCP graph from Graphite related to one or more of the
prefixes. Each url returns a JSON with graph data.
..a VLAN/Prefix page has DHCP graphs displayed if NAV has collected
some recent enough DHCP stats for IP addresses that intersect that
VLAN/Prefix.
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Feb 12, 2026

Quality Gate Passed Quality Gate passed

Issues
0 New issues
3 Accepted issues

Measures
0 Security Hotspots
No data about Coverage
0.0% Duplication on New Code

See analysis details on SonarQube Cloud

@jorund1 jorund1 changed the base branch from dhcpstats-backend to master February 12, 2026 12:42
@lunkwill42 lunkwill42 merged commit d73095e into Uninett:master Feb 12, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add functionality to NAV to graph DHCP lease stats on each VLAN page in NAV

3 participants