diff --git a/common/utils/T/.gitignore b/common/utils/T/.gitignore index 7ac6cc75bd..66f7cdb3d7 100644 --- a/common/utils/T/.gitignore +++ b/common/utils/T/.gitignore @@ -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 diff --git a/common/utils/T/DOC/T.md b/common/utils/T/DOC/T.md index e1ba0bae34..1ae6bd8283 100644 --- a/common/utils/T/DOC/T.md +++ b/common/utils/T/DOC/T.md @@ -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) diff --git a/common/utils/T/DOC/T/csv.md b/common/utils/T/DOC/T/csv.md new file mode 100644 index 0000000000..a5171c26c5 --- /dev/null +++ b/common/utils/T/DOC/T/csv.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 +``` + +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 ``. + +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 +``` diff --git a/common/utils/T/tracee/common/config/config_userapi.h b/common/utils/T/tracee/common/config/config_userapi.h index 56532a4056..8c3eb8b0d7 100644 --- a/common/utils/T/tracee/common/config/config_userapi.h +++ b/common/utils/T/tracee/common/config/config_userapi.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_ */ diff --git a/common/utils/T/tracer/CMakeLists.txt b/common/utils/T/tracer/CMakeLists.txt index 56568c905e..e347552d31 100644 --- a/common/utils/T/tracer/CMakeLists.txt +++ b/common/utils/T/tracer/CMakeLists.txt @@ -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) diff --git a/common/utils/T/tracer/Makefile b/common/utils/T/tracer/Makefile index ce99eabbe8..cedf8fdbcf 100644 --- a/common/utils/T/tracer/Makefile +++ b/common/utils/T/tracer/Makefile @@ -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 diff --git a/common/utils/T/tracer/csv.c b/common/utils/T/tracer/csv.c new file mode 100644 index 0000000000..486301062b --- /dev/null +++ b/common/utils/T/tracer/csv.c @@ -0,0 +1,188 @@ +#include +#include +#include +#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] ... \n" + "options:\n" + " -d this option is mandatory\n" + " -ip tracee's IP address (default %s)\n" + " -p tracee's port (default %d)\n" + " -f flush stdout after each printed line\n" + " -s \n" + " -t set timestamp name, use it as \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; +} diff --git a/common/utils/T/tracer/database.c b/common/utils/T/tracer/database.c index 347ca7cfcf..ec28f172a0 100644 --- a/common/utils/T/tracer/database.c +++ b/common/utils/T/tracer/database.c @@ -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);