Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions api/counter.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ yanet_get_chain_counters(
const char *chain_name
);

struct counter_handle_list *
yanet_get_nic_counters(struct dp_config *dp_config);

// Get module counters, optionally filtered by name.
struct counter_handle_list *
yanet_get_module_counters(
Expand Down
11 changes: 11 additions & 0 deletions controlplane/ffi/shm.go
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,17 @@ func (m *DPConfig) PerformanceCounters(
return result, nil
}

func (m *DPConfig) NICCounters() []CounterInfo {
counters := C.yanet_get_nic_counters(m.ptr)
defer C.yanet_counter_handle_list_free(counters)

if counters == nil {
return nil
}

return m.encodeCounters(counters)
}

type ModuleReference struct {
Device string
Pipeline string
Expand Down
14 changes: 14 additions & 0 deletions controlplane/internal/gateway/counters_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,20 @@ func (m *CountersService) Module(
return response, nil
}

func (m *CountersService) NIC(
ctx context.Context,
request *ynpb.NICCounterRequest,
) (*ynpb.CountersResponse, error) {
dpConfig := m.shm.DPConfig(m.instanceID)
counterValues := dpConfig.NICCounters()

response := &ynpb.CountersResponse{
Counters: m.encodeCounters(counterValues),
}

return response, nil
}

func (m *CountersService) Perf(
ctx context.Context,
request *ynpb.PerfCountersRequest,
Expand Down
3 changes: 3 additions & 0 deletions controlplane/ynpb/counters.proto
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@ service CountersService {
rpc Function(FunctionCountersRequest) returns (CountersResponse) {}
rpc Chain(ChainCountersRequest) returns (CountersResponse) {}
rpc Module(ModuleCountersRequest) returns (CountersResponse) {}
rpc NIC(NICCounterRequest) returns (CountersResponse) {}
rpc Perf(PerfCountersRequest) returns (PerfCountersResponse) {}
Comment on lines 11 to 15
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

The new rpc Nic is added to the public gRPC service, but there is no corresponding handler implemented in controlplane/internal/gateway/counters_service.go (so clients will get Unimplemented unless/ until it’s wired up). Please add the server-side method that calls DPConfig.NicCounters() (or remove the RPC until it’s supported end-to-end).

Copilot uses AI. Check for mistakes.
}

message DeviceCountersRequest { string device = 1; }

message NICCounterRequest { string device = 1; }

message PipelineCountersRequest {
string device = 1;
string pipeline = 2;
Expand Down
24 changes: 0 additions & 24 deletions dataplane/dataplane.c
Original file line number Diff line number Diff line change
Expand Up @@ -617,11 +617,7 @@ stat_thread(void *arg) {
struct rte_eth_xstat_name names[4096];
struct rte_eth_xstat xstats0[dataplane->device_count][4096];

struct rte_eth_stats stats0[dataplane->device_count];
for (uint16_t idx = 0; idx < dataplane->device_count; ++idx) {
rte_eth_stats_get(
dataplane->devices[idx].port_id, &stats0[idx]
);
rte_eth_xstats_get(
dataplane->devices[idx].port_id, xstats0[idx], 4096
);
Expand All @@ -631,26 +627,6 @@ stat_thread(void *arg) {
sleep(1);

for (uint16_t idx = 0; idx < dataplane->device_count; ++idx) {
struct rte_eth_stats stats1;
rte_eth_stats_get(
dataplane->devices[idx].port_id, &stats1
);
fprintf(log,
"dev %u ib %li ob %li ip %li op %li ie %li oe "
"%li\n",
idx,
(int64_t)(stats1.ibytes - stats0[idx].ibytes),
(int64_t)(stats1.obytes - stats0[idx].obytes),
(int64_t)(stats1.ipackets - stats0[idx].ipackets
),
(int64_t)(stats1.opackets - stats0[idx].opackets
),
(int64_t)(stats1.ierrors - stats0[idx].ierrors),
(int64_t)(stats1.oerrors - stats0[idx].oerrors)
);

memcpy(&stats0[idx], &stats1, sizeof(stats1));

struct rte_eth_xstat xstats1[4096];
rte_eth_xstats_get_names(
dataplane->devices[idx].port_id, names, 4096
Expand Down
121 changes: 66 additions & 55 deletions dataplane/worker.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@

#include <rte_ethdev.h>

#define NIC_STATS_FREQUENCY 1000

static void
worker_read(struct dataplane_worker *worker, struct packet_list *packets) {
struct worker_read_ctx *ctx = &worker->read_ctx;
Expand Down Expand Up @@ -271,6 +273,21 @@ worker_write(struct dataplane_worker *worker, struct packet_list *packets) {
}
}

void worker_unload_nic_stats(struct dataplane_worker *worker) {
struct rte_eth_stats stats1;
struct dp_worker *dp_worker = worker->dp_worker;

rte_eth_stats_get(worker->port_id, &stats1);
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

rte_eth_stats_get() return value is ignored. If it fails, stats1 contents are undefined and will be written into counters below. Check the return code and only update the shared counters on success (optionally record/log failures).

Suggested change
rte_eth_stats_get(worker->port_id, &stats1);
if (rte_eth_stats_get(worker->port_id, &stats1) != 0) {
return;
}

Copilot uses AI. Check for mistakes.

*dp_worker->nic_rx_bytes = stats1.ibytes;
*dp_worker->nic_tx_bytes = stats1.obytes;
*dp_worker->nic_rx_packets = stats1.ipackets;
*dp_worker->nic_tx_packets = stats1.opackets;
*dp_worker->nic_rx_errors = stats1.ierrors;
*dp_worker->nic_tx_errors = stats1.oerrors;
*dp_worker->nic_rx_nombuf = stats1.rx_nombuf;
}

static void
worker_loop_round(struct dataplane_worker *worker) {
// Initialize current worker time
Expand Down Expand Up @@ -313,7 +330,10 @@ worker_loop_round(struct dataplane_worker *worker) {
cp_config_gen->device_registry.registry.capacity;

while (1) {

if (*worker->dp_worker->iterations % NIC_STATS_FREQUENCY == 0) {
worker_unload_nic_stats(worker);
}

struct packet_front schedule_input[device_count];
for (uint64_t idx = 0; idx < device_count; ++idx)
packet_front_init(schedule_input + idx);
Expand Down Expand Up @@ -406,6 +426,32 @@ worker_thread_start(void *arg) {
return NULL;
}

static const struct {
const char *name;
uint64_t size;
const size_t *offset;
} worker_counter_info[] = {
{"iterations", 1, (const size_t[]){offsetof(struct dp_worker, iterations)}},
{"rx", 2, (const size_t[]){offsetof(struct dp_worker, rx_count), offsetof(struct dp_worker, rx_size)}},
{"tx", 2, (const size_t[]){offsetof(struct dp_worker, tx_count), offsetof(struct dp_worker, tx_size)}},
{"remote_rx", 1, (const size_t[]){offsetof(struct dp_worker, remote_rx_count)}},
{"remote_tx", 1, (const size_t[]){offsetof(struct dp_worker, remote_tx_count)}},
{"nic_rx", 2, (const size_t[]){offsetof(struct dp_worker, nic_rx_packets), offsetof(struct dp_worker, nic_rx_bytes)}},
{"nic_tx", 2, (const size_t[]){offsetof(struct dp_worker, nic_tx_packets), offsetof(struct dp_worker, nic_tx_bytes)}},
{"nic_rx_tx_errors", 2, (const size_t[]){offsetof(struct dp_worker, nic_rx_errors), offsetof(struct dp_worker, nic_tx_errors)}},
{"nic_rx_nombuf", 1, (const size_t[]){offsetof(struct dp_worker, nic_rx_nombuf)}},
};

uint64_t**
get_worker_field_ptr(struct dp_worker *worker, size_t info_index, size_t offset_index) {
return (uint64_t**)((char*)worker + worker_counter_info[info_index].offset[offset_index]);
}

#define ARRAY_SIZE(arr) (size_t)(sizeof(arr) / sizeof((arr)[0]))

static uint64_t
worker_counter_ids[ARRAY_SIZE(worker_counter_info)];

int
dataplane_worker_init(
struct dataplane *dataplane,
Expand Down Expand Up @@ -551,14 +597,14 @@ dataplane_worker_init(
counter_registry_init(
&dp_config->worker_counters, &dp_config->memory_context, 0
);

counter_registry_register(&dp_config->worker_counters, "iterations", 1);

counter_registry_register(&dp_config->worker_counters, "rx", 2);
counter_registry_register(&dp_config->worker_counters, "tx", 2);
counter_registry_register(&dp_config->worker_counters, "remote_rx", 2);

counter_registry_register(&dp_config->worker_counters, "remote_tx", 2);
for (size_t i = 0; i < ARRAY_SIZE(worker_counter_info); ++i) {
worker_counter_ids[i] = counter_registry_register(
&dp_config->worker_counters,
worker_counter_info[i].name,
worker_counter_info[i].size
);
}

return 0;

Expand All @@ -574,53 +620,18 @@ dataplane_worker_start(struct dataplane_worker *worker) {
struct dp_worker *dp_worker = worker->dp_worker;
struct dp_config *dp_config = worker->instance->dp_config;
// FIXME: do not use hard-coded counter identifiers
dp_worker->iterations = counter_get_address(
0, dp_worker->idx, ADDR_OF(&dp_config->worker_counter_storage)
);

dp_worker->rx_count =
counter_get_address(
1,
dp_worker->idx,
ADDR_OF(&dp_config->worker_counter_storage)
) +
0;
dp_worker->rx_size = counter_get_address(
1,
dp_worker->idx,
ADDR_OF(&dp_config->worker_counter_storage)
) +
1;

dp_worker->tx_count =
counter_get_address(
2,
dp_worker->idx,
ADDR_OF(&dp_config->worker_counter_storage)
) +
0;
dp_worker->tx_size = counter_get_address(
2,
dp_worker->idx,
ADDR_OF(&dp_config->worker_counter_storage)
) +
1;

dp_worker->remote_rx_count =
counter_get_address(
3,
dp_worker->idx,
ADDR_OF(&dp_config->worker_counter_storage)
) +
0;

dp_worker->remote_tx_count =
counter_get_address(
4,
dp_worker->idx,
ADDR_OF(&dp_config->worker_counter_storage)
) +
0;
for (size_t i = 0; i < ARRAY_SIZE(worker_counter_info); ++i) {
for (size_t j = 0; j < worker_counter_info[i].size; ++j) {
uint64_t **field_ptr = get_worker_field_ptr(dp_worker, i, j);
*field_ptr = counter_get_address(
worker_counter_ids[i],
dp_worker->idx,
ADDR_OF(&dp_config->worker_counter_storage)
) + j;
}
}


pthread_attr_t wrk_th_attr;
pthread_attr_init(&wrk_th_attr);
Expand Down
62 changes: 62 additions & 0 deletions lib/controlplane/agent/agent.c
Original file line number Diff line number Diff line change
Expand Up @@ -1648,6 +1648,68 @@ yanet_get_device_counters(
return list;
}

struct counter_handle_list *
yanet_get_nic_counters(struct dp_config *dp_config) {
const char *query[] = {
"nic_rx",
"nic_tx",
"nic_rx_tx_errors",
"nic_rx_nombuf",
};
int query_count = sizeof(query) / sizeof(query[0]);

struct counter_registry *counter_registry = &dp_config->worker_counters;
struct counter_storage *storage =
ADDR_OF(&dp_config->worker_counter_storage);

uint64_t count = counter_registry->count;
struct counter *names = ADDR_OF(&counter_registry->names);

uint64_t match_count = 0;

for (uint64_t idx = 0; idx < count; ++idx) {
if (!counter_name_matches_query(
names[idx].name, query, query_count
)) {
continue;
}

match_count++;
}

if (match_count == 0) {
return NULL;
}

struct counter_handle_list *list = (struct counter_handle_list *)malloc(
sizeof(struct counter_handle_list) +
sizeof(struct counter_handle) * match_count
);

if (list == NULL)
return NULL;
list->instance_count = ADDR_OF(&storage->allocator)->instance_count;
list->count = count;
struct counter_handle *handlers = list->counters;

uint64_t out_idx = 0;
for (uint64_t idx = 0; idx < count; ++idx) {
if (!counter_name_matches_query(
names[idx].name, query, query_count
)) {
continue;
}
strtcpy(handlers[out_idx].name, names[idx].name, 60);
handlers[out_idx].size = names[idx].size;
handlers[out_idx].gen = names[idx].gen;
handlers[out_idx].value_handle =
counter_get_value_handle(idx, storage);
out_idx++;
}
Comment on lines +1684 to +1708
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

yanet_get_nic_counters allocates count handlers and sets list->count = count, but only fills entries that match the query. If there are fewer (or zero) matches, the remaining handlers are left uninitialized and will be encoded/returned as garbage. Compute match_count first (like the existing query-based counter listing logic earlier in this file), allocate based on match_count, and set list->count = match_count (return NULL when match_count == 0).

Copilot uses AI. Check for mistakes.

return list;
}

struct counter_handle *
yanet_get_counter(struct counter_handle_list *counters, uint64_t idx) {
if (idx >= counters->count)
Expand Down
15 changes: 15 additions & 0 deletions lib/dataplane/config/zone.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,21 @@ struct dp_worker {
struct rte_mempool *rx_mempool;

uint8_t pad[24];

// trade-off: nic stats unload not often
// bcs put it in second cache line
Comment on lines +66 to +67
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

Comment uses informal abbreviations ("bcs", "unload not often") and is hard to parse. Please rephrase in clear English (e.g., explain the cache-line placement trade-off explicitly).

Suggested change
// trade-off: nic stats unload not often
// bcs put it in second cache line
// Trade-off: place NIC statistics in the second cache line
// because they are accessed less frequently than the fields above.

Copilot uses AI. Check for mistakes.
uint64_t *nic_rx_packets;
uint64_t *nic_rx_bytes;

uint64_t *nic_tx_packets;
uint64_t *nic_tx_bytes;

uint64_t *nic_rx_errors;
uint64_t *nic_tx_errors;

uint64_t *nic_rx_nombuf;

uint8_t pad2[8];
};
struct dp_config {
uint32_t instance_count;
Expand Down
Loading