Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

'show bgp detail' memory improvement #16771

Closed

Conversation

pguibert6WIND
Copy link
Member

@pguibert6WIND pguibert6WIND commented Sep 9, 2024

After having loaded a full route, the memory consumption still increases after executing the 'show bgp ipv4 json detail' command. The memory consumed is related to json objects stored for BGP as paths, standard and large communities.

The below pull request proposes to incrementally display each path detailed information, without relying on the json storage.

Memory consumed after BGP routes have been loaded:

dut-orwell-nianticvf# show ip route summary
Route Source         Routes               FIB  (vrf default)
kernel               1                    1
connected            2                    2
ebgp                 993276               992665
ibgp                 0                    0
------
Totals               993279               992668

root@dut-orwell-nianticvf:~# ps -aux | grep bgpd
root      106598 84.0 10.2 1224244 1044028 ?     Ssl  15:54   1:46 /usr/bin/bgpd -A 127.0.0.1 -M snmp -M rpki -M bmp

Memory consumed after the 'show bgp ipv4 json detail' command is executed, without fix:

root@dut-orwell-nianticvf:~# time vtysh -c "show bgp ipv4 detail json" > /tmp/aa.txt

real    0m25.336s
user    0m2.438s
sys     0m4.745s
root@dut-orwell-nianticvf:~# ps -aux | grep bgpd
root       16770 59.0 18.6 2080984 1900816 ?     Ssl  09:13   2:03 /usr/bin/bgpd -A 127.0.0.1 -M snmp -M rpki -M bmp

after fix:

root@dut-orwell-nianticvf:~# time vtysh -c "show bgp ipv4 detail json" > /tmp/aa.txt

real    0m33.106s
user    0m2.594s
sys     0m4.533s
root@dut-sureau-nianticvf:~# ps -aux | grep bgpd
root       45983 45.4 13.6 1567712 1387988 ?     Ssl  11:24   2:10 /usr/bin/bgpd -A 127.0.0.1 -M snmp -M rpki -M bmp

@ton31337
Copy link
Member

ton31337 commented Sep 9, 2024

I'm a bit confused, is this an issue of the JSON library or our implementation?

@pguibert6WIND
Copy link
Member Author

pguibert6WIND commented Sep 9, 2024

I'm a bit confused, is this an issue of the JSON library or our implementation?

Json structs are locally allocated at show time in BGP as paths. This makes increase of memory used, whereas we already struggle with memory with BGP handling million of routes.

I had the possibility to allocate json at startup. In this way, even before show time, we would have reached a memory size that would not move, event after.
But I did not want that, because it takes a lot of memory ( 113K json AS paths structs if I remember well).

So I decided to not store json.
Then I had two options:
a- either allocate and free json objects after the show command.
b- or use vty, and avoid minimize the usage of json objects

I took the choice of using b).
allocating and freeing variable size objects is always a stress for Linux processed, and I did not want to increase the VMSIZE, only because of json.

Copy link
Contributor

@mjstapp mjstapp left a comment

Choose a reason for hiding this comment

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

the "before" and "after" examples in the description: where on master were those taken? did both include the "incremental vtysh" change that we merged recently? I'm wondering whether there's something we might do to extend that concept for large json output, if necessary.

Copy link
Contributor

@mjstapp mjstapp left a comment

Choose a reason for hiding this comment

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

Just at the top level, I'm not keen to try to manually do json encoding inside bgpd. I'd prefer to see something incremental, for example, that still used the common library in some more efficient way.

@pguibert6WIND
Copy link
Member Author

the "before" and "after" examples in the description: where on master were those taken? did both include the "incremental vtysh" change that we merged recently? I'm wondering whether there's something we might do to extend that concept for large json output, if necessary.

Measures have been done on the top of master (with spike commit + json incremental fixes partial for BGP).

@pguibert6WIND
Copy link
Member Author

pguibert6WIND commented Sep 9, 2024

Just at the top level, I'm not keen to try to manually do json encoding inside bgpd. I'd prefer to see something incremental, for example, that still used the common library in some more efficient way.

json_object_set_serializer() was an API that could help to implement our output for each kind of json object. For instance, this could help by allocating enough memory and storing the output in the buffer. But the result was the same, I was allocating a lot of memory. Alternatively, I did not find how to pass vty pointer in those functions.
I tried a second method by parsing the json objects by myself. But this kind of work needs to be done in the json-c library , not outside, because I access private structures.

I think that on a long term basis, all this code will be replaced by a real state model defined in YANG. But today, we do not have this in BGP.

@pguibert6WIND pguibert6WIND force-pushed the bgp_detail_no_json_alloc branch 4 times, most recently from a057c2e to 605766d Compare September 10, 2024 08:54
@pguibert6WIND pguibert6WIND marked this pull request as draft September 10, 2024 09:41
Copy link

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@mjstapp
Copy link
Contributor

mjstapp commented Sep 10, 2024

Have you looked at the json-c library? it looks like it would be possible to do our own string output walk through a tree of json objects, keep track of any container objects that have been partially printed, and free inner objects once they've been printed. I'll bet that would a) take fewer lines of change, and b) be reusable generally.

@pguibert6WIND
Copy link
Member Author

Have you looked at the json-c library? it looks like it would be possible to do our own string output walk through a tree of json objects, keep track of any container objects that have been partially printed, and free inner objects once they've been printed. I'll bet that would a) take fewer lines of change, and b) be reusable generally.

The main problem is we store json objects in BGP aspath/communities/large communities structure, and it is too expensive.
At least, I think we should get rid of this pre-allocation.

@pguibert6WIND
Copy link
Member Author

pguibert6WIND commented Sep 10, 2024

Have you looked at the json-c library? it looks like it would be possible to do our own string output walk through a tree of json objects, keep track of any container objects that have been partially printed, and free inner objects once they've been printed. I'll bet that would a) take fewer lines of change, and b) be reusable generally.

The main problem is we store json objects in BGP aspath/communities/large communities structure, and it is too expensive. At least, I think we should get rid of this pre-allocation.

I made a branch with using extended API.
https://github.com/pguibert6WIND/frr/tree/bgp_detail_no_json_alloc_draft

With a custom release using serialized code for aspath only (community and large community code has been removed) we demonstrate that the memory taken is similar to before the 'show bgp ipv4 json detail' command.

before the show

root@dut-sureau-nianticvf:~# ps -aux | grep bgpd
root       11213 60.0 10.2 1224168 1044272 ?     Ssl  09:52   1:42 /usr/bin/bgpd -A 127.0.0.1 -M snmp -M rpki -M bmp

during the show

root@dut-sureau-nianticvf:~# time vtysh -c "show bgp ipv4 json detail" > /tmp/aa.txt  
real    0m26.592s
user    0m2.384s
sys     0m5.100s

after the show

root@dut-sureau-nianticvf:~# ps -aux | grep bgpd
root       11213 39.7 10.3 1234176 1054420 ?     Ssl  09:52   2:05 /usr/bin/bgpd -A 127.0.0.1 -M snmp -M rpki -M bmp

When using the 'show bgp ipv4 json detail' against a full route,
the BGP memory consumed increases a lot, and is never restored.

Before the show:
> root@dut-orwell-nianticvf:~# ps -aux | grep bgpd
> root       12263 10.8 10.1 1213920 1033452 ?     Ssl  10:43   1:42 /usr/bin/bgpd -A 127.0.0.1 -M snmp -M rpki -M bmp

After the show:
> root@dut-orwell-nianticvf:~# ps -aux | grep bgpd
> root       11772  4.9 19.3 2150668 1970548 ?     Ssl  08:59   2:09 /usr/bin/bgpd -A 127.0.0.1 -M snmp -M rpki -M bmp

This is not a memory leak, since applying multiple times the
command does not increase the memory.
What happens is that some internal structures of BGP store
a json structure: AsPath, Communities, Large Communities.

This series of commits intends to do the following for the
following objects: ASPaths, communities and large communities:
- replace the usage of the internal json structure by a dynamically
built json object.
- remove the no more used json object referenced in the BGP internals.

That first commit addresses the case with ASpaths: instead of
relying on the internal json structure, the json aspath
objects are allocated and freed at usage.

Signed-off-by: Philippe Guibert <[email protected]>
The aspath library stores a json pointer that is no more
used. Remove the 'make_json' argument of the associated
functions. Remove the 'json' attribute from the bgp_aspath
structure.

Signed-off-by: Philippe Guibert <[email protected]>
Create the community_json_list() function, that stores the
community list output in a passed buffer, and optionally
in the json community list passed array.

This commit does not have any functional impact.

Signed-off-by: Philippe Guibert <[email protected]>
The community_get_json() API is used to return a json object.
At display time, this object will use the community_list_json_to_string()
function to handle the output.

Signed-off-by: Philippe Guibert <[email protected]>
The bgp community list library stores a json pointer that is no more
used. Remove the 'make_json' argument of the associated
functions. Remove the 'json' attribute from the bgp community
structure.

Signed-off-by: Philippe Guibert <[email protected]>
Create the lcommunity_json_list() function, that returns the
large community list output in a returned buffer, and optionally
in the json large community list passed array.

This commit does not have any functional impact.

Signed-off-by: Philippe Guibert <[email protected]>
@pguibert6WIND pguibert6WIND force-pushed the bgp_detail_no_json_alloc branch from 2ef66bd to f0c97fa Compare September 11, 2024 16:39
The lcommunity_get_json() API is used to return a json object.
At display time, this object will use the lcommunity_json_to_string()
function to handle the output.

Signed-off-by: Philippe Guibert <[email protected]>
The bgp large community library stores a json pointer that is no more
used. Remove the 'make_json' argument of the associated
functions. Remove the 'json' attribute from the bgp large community
structure.

Signed-off-by: Philippe Guibert <[email protected]>
@pguibert6WIND pguibert6WIND force-pushed the bgp_detail_no_json_alloc branch from f0c97fa to eb6b0a1 Compare September 11, 2024 16:58
@pguibert6WIND pguibert6WIND marked this pull request as ready for review September 11, 2024 20:46
@pguibert6WIND
Copy link
Member Author

The global memory used before the show


System allocator statistics:
  Total heap allocated:  980 MiB
  Holding block headers: 24 MiB
  Used small blocks:     0 bytes
  Used ordinary blocks:  979 MiB
  Free small blocks:     336 KiB
  Free ordinary blocks:  1877 KiB
  Ordinary blocks:       6483
  Small blocks:          2794
  Holding blocks:        22

after the show

System allocator statistics:
  Total heap allocated:  1316 MiB
  Holding block headers: 24 MiB
  Used small blocks:     0 bytes
  Used ordinary blocks:  979 MiB
  Free small blocks:     336 KiB
  Free ordinary blocks:  337 MiB
  Ordinary blocks:       6522
  Small blocks:          2798
  Holding blocks:        22

This API proposes to use the sprintbuf() method to facilitate
the forge of buffers in json.

Note that this buffer is limited to 128 bytes, so it can not
be used everywhere as it without any checks.

Signed-off-by: Philippe Guibert <[email protected]>
@pguibert6WIND
Copy link
Member Author

pguibert6WIND commented Sep 12, 2024

Have you looked at the json-c library? it looks like it would be possible to do our own string output walk through a tree of json objects, keep track of any container objects that have been partially printed, and free inner objects once they've been printed. I'll bet that would a) take fewer lines of change, and b) be reusable generally.

@mjstapp , please review
The implementation uses json-c API to create our own string.
Solution is not perfect.

  • VMsize went from 1224244 to 1567712 instead of 2080984.
  • and the time to produce json is increased of a few seconds. (from 25 seconds to 33 seconds).

@pguibert6WIND
Copy link
Member Author

closing this pull request for clarity. please go to:
#16880

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.

3 participants