Merge remote-tracking branch 'origin/t-tracer-csv' into integration_2026_w04 (!3861)
add a csv T tracer Add a CSV T tracer to dump stuff to a CSV file.
This commit is contained in:
@@ -16,5 +16,8 @@ tracer/extract_input_subframe
|
||||
tracer/extract_output_subframe
|
||||
tracer/extract
|
||||
tracer/multi
|
||||
tracer/t_tracer_app_gnb
|
||||
tracer/t_tracer_app_ue
|
||||
tracer/csv
|
||||
tracee/tracee
|
||||
incgen
|
||||
|
||||
@@ -17,4 +17,5 @@ It is made of two main parts:
|
||||
* [MAC PDUs and wireshark](./T/wireshark.md)
|
||||
* [The *enb* tracer](./T/enb.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)
|
||||
|
||||
@@ -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
|
||||
```
|
||||
@@ -8,7 +8,7 @@ typedef int paramdef_t;
|
||||
#endif
|
||||
#define CMDLINE_TTRACEPARAMS_DESC {}
|
||||
|
||||
#define config_get(dummy, ...) (void)(dummy)
|
||||
#define config_get(...) /**/
|
||||
#define config_process_cmdline(...) /**/
|
||||
|
||||
#endif /* _CONFIG_USERAPI_H_ */
|
||||
|
||||
@@ -22,6 +22,9 @@ target_link_libraries(extract PRIVATE tracer_utils)
|
||||
add_executable(macpdu2wireshark macpdu2wireshark.c)
|
||||
target_link_libraries(macpdu2wireshark PRIVATE tracer_utils)
|
||||
|
||||
add_executable(csv csv.c)
|
||||
target_link_libraries(csv PRIVATE tracer_utils)
|
||||
|
||||
add_executable(multi multi.c)
|
||||
target_link_libraries(multi PRIVATE tracer_utils T)
|
||||
target_include_directories(multi PRIVATE ..)
|
||||
@@ -38,7 +41,7 @@ add_custom_target(T_tools)
|
||||
add_dependencies(T_tools
|
||||
record replay extract_config extract_input_subframe
|
||||
extract_output_subframe extract macpdu2wireshark multi
|
||||
textlog)
|
||||
textlog csv)
|
||||
add_dependencies(nr-softmodem T_tools)
|
||||
add_dependencies(nr-uesoftmodem T_tools)
|
||||
add_dependencies(lte-softmodem T_tools)
|
||||
|
||||
@@ -8,7 +8,7 @@ XLIBS=-lX11 -lpng -lXft
|
||||
|
||||
all: record replay extract_config textlog enb ue vcd macpdu2wireshark \
|
||||
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
|
||||
$(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
|
||||
$(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
|
||||
|
||||
../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 extract_config macpdu2wireshark extract_input_subframe
|
||||
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 view && $(MAKE) clean
|
||||
cd logger && $(MAKE) clean
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -403,7 +403,7 @@ static int onoff_id(database *d, char *name, int *a, int onoff)
|
||||
i = my_bsearch(&(id){.name=name}, d->i, d->isize, sizeof(id), id_cmp);
|
||||
if (i == NULL) return 0;
|
||||
a[i->id] = onoff;
|
||||
printf("turning %s %s\n", onoff ? "ON" : "OFF", name);
|
||||
fprintf(stderr, "turning %s %s\n", onoff ? "ON" : "OFF", name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -424,7 +424,7 @@ void on_off(void *_d, char *item, int *a, int onoff)
|
||||
int i;
|
||||
if (item == NULL) {
|
||||
for (i = 0; i < d->isize; i++) a[i] = onoff;
|
||||
printf("turning %s all traces\n", onoff ? "ON" : "OFF");
|
||||
fprintf(stderr, "turning %s all traces\n", onoff ? "ON" : "OFF");
|
||||
return;
|
||||
}
|
||||
done = onoff_group(d, item, a, onoff);
|
||||
|
||||
Reference in New Issue
Block a user