EDIT 2021 - an updated data provider performance demo is available here:
As this post explains for the get_object() callback:
In order to improve the performance of the data provider API callbacks, for both external configuration as well as operational data, when dealing with lists with many leaf elements in them, it is recommended to provide the implementation of the get_object() API. This will reduce the number of round trip calls from the ConfD daemon process for retrieving the leaf elements of each instance in your list as they will all be fetched with a single API call as opposed to via multiple get_elem() calls.
In this post we will further reduce the number of round trip calls needed from the ConfD daemon process for retrieving a whole list of instances or a chunk of instances can be further reduced to getting all instances in one swoop by implementing the get_next_object() API.
The changes needed to add the get_next_object() callback support to the 5-c_stats example will be described in this posting. All the changes will be made to the arpstat.c file of the example.
In the main() function, you will add the registration of your get_next_object() callback as follows:
data.get_next_object = get_next_object;
Then it is cheap and handy to have the number of ARP instances available in our arpdata struct:
#define N_LEAFS 5
struct arpdata {
struct aentry *arp_entries;
struct timeval lastparse;
int n_ae;
};
We now need to increase and reset the number of ARP instances, n_ae, in our free_arp() and add_aentry() functions:
static void free_arp(struct arpdata *dp)
{
struct aentry *ae = dp->arp_entries;
while (ae) {
struct aentry *next = ae->next;
if(ae->hwaddr) free(ae->hwaddr);
if(ae->iface) free(ae->iface);
free(ae);
ae = next;
}
dp->arp_entries = NULL;
dp->n_ae = 0;
}
/* add an entry, keeping the list ordered */
static void add_aentry(struct aentry **first, struct aentry *new)
{
struct aentry **prev = first;
while (*prev != NULL &&
(memcmp(&new->ip4, &(*prev)->ip4, sizeof(struct in_addr)) > 0 ||
(memcmp(&new->ip4, &(*prev)->ip4, sizeof(struct in_addr)) == 0 &&
strcmp(new->iface, (*prev)->iface) > 0))) {
prev = &(*prev)->next;
}
new->next = *prev;
*prev = new;
}
In the existing run_arp() function we need to increase the “n_ae” count when we call “add_aentry()”
@@ -180,6 +184,7 @@
}
add_aentry(&dp->arp_entries, ae);
+ dp->n_ae++;
} else {
/* skip this entry */
free(ae);
Finally, the implementation of the get_next_object() callback is as follows:
/* Keypath example */
/* /arpentries/arpe */
/* 2 1 */
static int get_next_object(struct confd_trans_ctx *tctx,
confd_hkeypath_t *keypath, long next)
{
int pos, i;
confd_value_t *v;
struct confd_next_object *obj;
struct arpdata *dp = tctx->t_opaque;
int n_list_entries;
struct aentry *ae;
if (need_arp(dp)) {
if (run_arp(dp) == CONFD_ERR)
return CONFD_ERR;
}
n_list_entries = dp->n_ae;
ae = dp->arp_entries;
if (next == -1) {
pos = 0; /* ConfD wants to get the whole table / list from the beginning */
} else if (next < n_list_entries) {
pos = next; /* ConfD wants the table / list from a specific index */
} else { /* next == n_list_entries, return nothing */
confd_data_reply_next_object_array(tctx, NULL, -1, -1);
return CONFD_OK;
}
for(i = 0; i < pos; i++)
ae = ae->next;
obj = malloc(sizeof(struct confd_next_object) * (n_list_entries + 2));
v = (confd_value_t *) malloc(sizeof(confd_value_t) * n_list_entries * N_LEAFS);
for (i = 0; pos + i < n_list_entries; i++) { /* Collect all the rows in the table / list entries */
obj[i].v = &v[i * N_LEAFS];
CONFD_SET_IPV4(&(obj[i].v[0]), ae->ip4);
CONFD_SET_STR(&(obj[i].v[1]), ae->iface);
if (ae->hwaddr == NULL) {
CONFD_SET_NOEXISTS(&(obj[i].v[2]));
} else {
CONFD_SET_STR(&(obj[i].v[2]), ae->hwaddr);
}
CONFD_SET_BOOL(&(obj[i].v[3]), ae->perm);
CONFD_SET_BOOL(&(obj[i].v[4]), ae->pub);
obj[i].n = N_LEAFS;
obj[i].next = -1;
ae=ae->next;
}
if (pos + i == n_list_entries)
obj[i++].v = NULL;
confd_data_reply_next_object_arrays(tctx, obj, i, 0); /* Reply with an array of object arrays. I.e. the whole table / list */
free(v);
free(obj);
return CONFD_OK;
}
After the above changes are made to the arpstat.c file, you will then be able to rebuild your project and see the get_next_object() callback in action through the TRACE statements of the arpstat program.
Say we have an ARP table with eight entries:
$ arp
Address HWtype HWaddress Flags Mask Iface
apps-net0-5.test.com ether dd:aa:aa:aa:aa:aa CM eth1
apps-net0-2.test.com ether aa:aa:aa:aa:aa:aa CM eth0
apps-net15-14.test.com ether 00:50:56:f3:13:d2 C eth2
apps-net0-4.test.com ether cc:aa:aa:aa:aa:aa CM eth1
apps-net0-6.test.com ether ee:aa:aa:aa:aa:aa CM eth1
apps-net0-3.test.com ether ff:aa:aa:aa:aa:aa CM eth1
apps-net0-3.test.com ether bb:aa:aa:aa:aa:aa CM eth0
apps-net0-1.test.com ether 00:50:56:f7:9d:e2 C eth2
We <GET>
the entire list over NETCONF using the netconf-console tool that comes with ConfD:
$ netconf-console --get -x '/arpentries/arpe'<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="1">
<data>
<arpentries xmlns="http://tail-f.com/ns/example/arpe">
<arpe>
<ip>172.16.171.2</ip>
<ifname>eth2</ifname>
<hwaddr>00:50:56:f7:9d:e2</hwaddr>
<permanent>false</permanent>
<published>false</published>
</arpe>
<arpe>
<ip>172.16.171.3</ip>
<ifname>eth0</ifname>
<hwaddr>aa:aa:aa:aa:aa:aa</hwaddr>
<permanent>true</permanent>
<published>false</published>
</arpe>
<arpe>
<ip>172.16.171.4</ip>
<ifname>eth0</ifname>
<hwaddr>bb:aa:aa:aa:aa:aa</hwaddr>
<permanent>true</permanent>
<published>false</published>
</arpe>
<arpe>
<ip>172.16.171.4</ip>
<ifname>eth1</ifname>
<hwaddr>ff:aa:aa:aa:aa:aa</hwaddr>
<permanent>true</permanent>
<published>false</published>
</arpe>
<arpe>
<ip>172.16.171.5</ip>
<ifname>eth1</ifname>
<hwaddr>cc:aa:aa:aa:aa:aa</hwaddr>
<permanent>true</permanent>
<published>false</published>
</arpe>
<arpe>
<ip>172.16.171.6</ip>
<ifname>eth1</ifname>
<hwaddr>dd:aa:aa:aa:aa:aa</hwaddr>
<permanent>true</permanent>
<published>false</published>
</arpe>
<arpe>
<ip>172.16.171.7</ip>
<ifname>eth1</ifname>
<hwaddr>ee:aa:aa:aa:aa:aa</hwaddr>
<permanent>true</permanent>
<published>false</published>
</arpe>
<arpe>
<ip>172.16.171.254</ip>
<ifname>eth2</ifname>
<hwaddr>00:50:56:f3:13:d2</hwaddr>
<permanent>false</permanent>
<published>false</published>
</arpe>
</arpentries>
</data>
</rpc-reply>
The entire list was fetched in one single swoop by ConfD from the application using the get_next_object() callback as we can see from the TRACE statements of the arpstat program.:
TRACE New user session: 1 for user:admin ctx:netconf --> CONFD_OK
TRACE CALL trans init(thandle=1,mode="r",db=running) --> CONFD_OK
TRACE CALL data get_next_object(thandle=1, /arpentries/arpe, -1) --> CONFD_OK
TRACE CALL trans finish(thandle=1) --> CONFD_OK
TRACE Close user sess 1
--> CONFD_OK