T tracer: add a csv tracer

This commit is contained in:
Cedric Roux
2026-01-19 11:32:40 +01:00
parent 102965a669
commit 34a58676ce
6 changed files with 301 additions and 3 deletions
+1
View File
@@ -16,5 +16,6 @@ tracer/extract_input_subframe
tracer/extract_output_subframe tracer/extract_output_subframe
tracer/extract tracer/extract
tracer/multi tracer/multi
tracer/csv
tracee/tracee tracee/tracee
incgen incgen
+1
View File
@@ -17,4 +17,5 @@ It is made of two main parts:
* [MAC PDUs and wireshark](./T/wireshark.md) * [MAC PDUs and wireshark](./T/wireshark.md)
* [The *enb* tracer](./T/enb.md) * [The *enb* tracer](./T/enb.md)
* [The *to_vcd* tracer](./T/to_vcd.md) * [The *to_vcd* tracer](./T/to_vcd.md)
* [The *csv* tracer](./T/csv.md)
* [Tutorial: how to create a new trace](./T/howto_new_trace.md) * [Tutorial: how to create a new trace](./T/howto_new_trace.md)
+102
View File
@@ -0,0 +1,102 @@
# csv
This tracer is used to dump information of a single trace to a CSV.
The basic usage is:
```shell
cd common/utils/T/tracer
./csv -d ../T_messages.txt <TRACE> <fields>
```
The available options include `-t` to print a timestamp (from the event
timestamp, i.e., it also works with trace files procuded by `record`, showing
the time when the event has been recorded initially) and `-s` to specify a
separator between fields (use `-s $'\t'` to pass a TAB character; this is
shell dependent, may be different on your setup).
To print a timestamp, you choose the name with `-t` and use the chosen
name in `<fields>`.
For example, with the gNB trace `GNB_PHY_DL_TICK`, if you run:
```
./csv -d ../T_messages.txt GNB_PHY_DL_TICK frame slot
```
You get the following output:
```
frame,slot
974,12
974,13
974,14
974,15
974,16
974,17
974,18
974,19
975,0
[...]
```
Now, to add a timestamp, you can run:
```
./csv -d ../T_messages.txt -t time GNB_PHY_DL_TICK time frame slot
```
Producing:
```
time,frame,slot
11:12:30.894336,49,17
11:12:30.894856,49,18
11:12:30.895341,49,19
11:12:30.895864,50,0
11:12:30.896394,50,1
11:12:30.896875,50,2
11:12:30.897378,50,3
11:12:30.897876,50,4
11:12:30.898427,50,5
11:12:30.898876,50,6
[...]
```
You can use whatever name you want and you can print the timestamp at any position.
For example, running (we also set the separator as `/` instead of the default `,`):
```
./csv -d ../T_messages.txt -s '/' -t ts GNB_PHY_DL_TICK frame ts slot
```
Prints:
```
frame/ts/slot
100/11:20:14.525125/6
100/11:20:14.525650/7
100/11:20:14.526137/8
100/11:20:14.526616/9
100/11:20:14.527116/10
100/11:20:14.527614/11
100/11:20:14.528142/12
100/11:20:14.528648/13
100/11:20:14.529130/14
[...]
```
Note: to generate those traces, the gNB was run as:
```
sudo ./nr-softmodem -E -O ~/b78.conf --T_stdout 2
```
The important part is `--T_stdout 2` to activate the T tracer and allow
`csv` to collect logs.
For more help, run:
```shell
./csv -h
```
+4 -1
View File
@@ -22,6 +22,9 @@ target_link_libraries(extract PRIVATE tracer_utils)
add_executable(macpdu2wireshark macpdu2wireshark.c) add_executable(macpdu2wireshark macpdu2wireshark.c)
target_link_libraries(macpdu2wireshark PRIVATE tracer_utils) target_link_libraries(macpdu2wireshark PRIVATE tracer_utils)
add_executable(csv csv.c)
target_link_libraries(csv PRIVATE tracer_utils)
add_executable(multi multi.c) add_executable(multi multi.c)
target_link_libraries(multi PRIVATE tracer_utils T) target_link_libraries(multi PRIVATE tracer_utils T)
target_include_directories(multi PRIVATE ..) target_include_directories(multi PRIVATE ..)
@@ -38,7 +41,7 @@ add_custom_target(T_tools)
add_dependencies(T_tools add_dependencies(T_tools
record replay extract_config extract_input_subframe record replay extract_config extract_input_subframe
extract_output_subframe extract macpdu2wireshark multi extract_output_subframe extract macpdu2wireshark multi
textlog) textlog csv)
add_dependencies(nr-softmodem T_tools) add_dependencies(nr-softmodem T_tools)
add_dependencies(nr-uesoftmodem T_tools) add_dependencies(nr-uesoftmodem T_tools)
add_dependencies(lte-softmodem T_tools) add_dependencies(lte-softmodem T_tools)
+5 -2
View File
@@ -8,7 +8,7 @@ XLIBS=-lX11 -lpng -lXft
all: record replay extract_config textlog enb ue vcd macpdu2wireshark \ all: record replay extract_config textlog enb ue vcd macpdu2wireshark \
extract_input_subframe extract_output_subframe to_vcd extract multi \ extract_input_subframe extract_output_subframe to_vcd extract multi \
gnb t_tracer_app_gnb t_tracer_app_ue gnb_mac gnb t_tracer_app_gnb t_tracer_app_ue gnb_mac csv
record: utils.o record.o database.o configuration.o record: utils.o record.o database.o configuration.o
$(CC) $(CFLAGS) -o record $^ $(LIBS) $(CC) $(CFLAGS) -o record $^ $(LIBS)
@@ -77,6 +77,9 @@ gnb_mac: gnb_mac.o gui/gui.a view/view.a logger/logger.a filter/filter.a \
utils.o database.o configuration.o handler.o event.o utils.o database.o configuration.o handler.o event.o
$(CC) $(CFLAGS) -o gnb_mac $^ $(LIBS) $(XLIBS) $(CC) $(CFLAGS) -o gnb_mac $^ $(LIBS) $(XLIBS)
csv: csv.o database.o utils.o handler.o event.o configuration.o
$(CC) $(CFLAGS) -o $@ $^ $(LIBS)
multi.o: ../incgen/T_IDs.h multi.o: ../incgen/T_IDs.h
../incgen/T_IDs.h: ../T_messages.txt ../incgen/T_IDs.h: ../T_messages.txt
@@ -103,7 +106,7 @@ clean:
rm -f *.o core tracer_remote textlog enb ue vcd record replay rm -f *.o core tracer_remote textlog enb ue vcd record replay
rm -f extract_config macpdu2wireshark extract_input_subframe rm -f extract_config macpdu2wireshark extract_input_subframe
rm -f extract_output_subframe to_vcd extract multi gnb rm -f extract_output_subframe to_vcd extract multi gnb
rm -f t_tracer_app_gnb t_tracer_app_ue gnb_mac rm -f t_tracer_app_gnb t_tracer_app_ue gnb_mac csv
cd gui && $(MAKE) clean cd gui && $(MAKE) clean
cd view && $(MAKE) clean cd view && $(MAKE) clean
cd logger && $(MAKE) clean cd logger && $(MAKE) clean
+188
View File
@@ -0,0 +1,188 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "database.h"
#include "event.h"
#include "handler.h"
#include "configuration.h"
#define DEFAULT_IP "127.0.0.1"
#define DEFAULT_PORT 2021
#define TIMESTAMP_FIELD -1
typedef struct {
int *fields;
enum event_arg_type *type;
int count;
} ev_data;
int do_flush = 0;
char *separator = ",";
void csv(void *_d, event e)
{
ev_data *d = _d;
for (int i = 0; i < d->count; i++) {
if (d->fields[i] == TIMESTAMP_FIELD) {
struct tm *t = localtime(&e.sending_time.tv_sec);
printf("%2.2d:%2.2d:%2.2d.%6.6ld",
t->tm_hour, t->tm_min, t->tm_sec, e.sending_time.tv_nsec / 1000);
} else switch (d->type[i]) {
case EVENT_INT: printf("%d", e.e[d->fields[i]].i); break;
case EVENT_ULONG: printf("%ld", e.e[d->fields[i]].ul); break;
case EVENT_FLOAT: printf("%g", e.e[d->fields[i]].f); break;
case EVENT_STRING: printf("%s", e.e[d->fields[i]].s); break;
default: fprintf(stderr, "fatal: unsupported type\n"); exit(1);
}
if (i != d->count - 1)
printf("%s", separator);
}
printf("\n");
if (do_flush)
fflush(stdout);
}
void usage(void)
{
printf(
"usage:\n"
" [options] <event name> <event field 1> ... <event field n>\n"
"options:\n"
" -d <database file> this option is mandatory\n"
" -ip <IP address> tracee's IP address (default %s)\n"
" -p <port> tracee's port (default %d)\n"
" -f flush stdout after each printed line\n"
" -s <separator (default ,)>\n"
" -t <timestamp name> set timestamp name, use it as <event field x>\n",
DEFAULT_IP,
DEFAULT_PORT
);
exit(1);
}
int main(int n, char **v)
{
char *database_filename = NULL;
void *database;
event_handler *h;
char *ip = DEFAULT_IP;
int port = DEFAULT_PORT;
char *timestamp_field_name = NULL;
char *event_name = NULL;
char **event_fields = NULL;
int count = 0;
for (int i = 1; i < n; i++) {
if (!strcmp(v[i], "-h") || !strcmp(v[i], "--help")) usage();
if (!strcmp(v[i], "-d")) { if(i>n-2)usage(); database_filename = v[++i]; continue; }
if (!strcmp(v[i], "-ip")) { if(i>n-2)usage(); ip = v[++i]; continue; }
if (!strcmp(v[i], "-p")) { if(i>n-2)usage(); port = atoi(v[++i]); continue; }
if (!strcmp(v[i], "-f")) { do_flush = 1; continue; }
if (!strcmp(v[i], "-s")) { if(i>n-2)usage(); separator = v[++i]; continue; }
if (!strcmp(v[i], "-t")) { if(i>n-2)usage(); timestamp_field_name= v[++i]; continue; }
if (!event_name) { event_name = v[i]; continue; }
count++;
event_fields = realloc(event_fields, count * sizeof(char *)); if (!event_fields) abort();
event_fields[count - 1] = v[i];
}
if (event_name == NULL || count == 0) {
fprintf(stderr, "fatal: provide an event and the fields of this event you want to trace\n");
usage();
}
ev_data d = { 0 };
d.count = count;
d.fields = malloc(count * sizeof(int)); if (!d.fields) abort();
d.type = malloc(count * sizeof(enum event_arg_type)); if (!d.type) abort();
if (database_filename == NULL) {
printf("ERROR: provide a database file (-d)\n");
printf("use -h for help on usage\n");
exit(1);
}
database = parse_database(database_filename);
load_config_file(database_filename);
h = new_handler(database);
/* setup the trace - event name and event fields */
int ev_id = event_id_from_name(database, event_name);
database_event_format f = get_format(database, ev_id);
for (int i = 0; i < count; i++) {
int ev;
if (timestamp_field_name && !strcmp(event_fields[i], timestamp_field_name)) {
d.fields[i] = TIMESTAMP_FIELD;
continue;
}
for (ev = 0; ev < f.count; ev++)
if (!strcmp(f.name[ev], event_fields[i]))
break;
if (ev == f.count) {
fprintf(stderr, "fatal: field '%s' not found in event '%s'\n", event_fields[i], event_name);
exit(1);
}
d.fields[i] = ev;
if (!strcmp(f.type[ev], "int")) {
d.type[i] = EVENT_INT;
} else if (!strcmp(f.type[ev], "ulong")) {
d.type[i] = EVENT_ULONG;
} else if (!strcmp(f.type[ev], "float")) {
d.type[i] = EVENT_FLOAT;
} else if (!strcmp(f.type[ev], "string")) {
d.type[i] = EVENT_STRING;
} else {
fprintf(stderr, "fatal: field '%s': unsupported field type '%s'\n", f.name[ev], f.type[ev]);
exit(1);
}
}
/* connect to tracee, activate wanted trace */
int in = connect_to(ip, port);
char mt = 1;
int number_of_events = number_of_ids(database);
int *is_on = calloc(number_of_events, sizeof(int));
if (is_on == NULL) {
printf("ERROR: out of memory\n");
exit(1);
}
on_off(database, event_name, is_on, 1);
/* activate selected trace */
if (socket_send(in, &mt, 1) == -1 ||
socket_send(in, &number_of_events, sizeof(int)) == -1 ||
socket_send(in, is_on, number_of_events * sizeof(int)) == -1) {
printf("ERROR: socket_send failed\n");
exit(1);
}
free(is_on);
register_handler_function(h, ev_id, csv, &d);
OBUF ebuf = {.osize = 0, .omaxsize = 0, .obuf = NULL};
for (int i = 0; i < count; i++) {
printf("%s", event_fields[i]);
if (i != count - 1)
printf("%s", separator);
}
printf("\n");
if (do_flush)
fflush(stdout);
/* read messages */
while (1) {
event e;
e = get_event(in, &ebuf, database);
if (e.type == -1) break;
handle_event(h, e);
}
return 0;
}