nuttx-apps/system/trace/trace.c
yinshengkai 95b0515c30 trace: unify ftrace and atrace output formats
Signed-off-by: yinshengkai <yinshengkai@xiaomi.com>
2023-08-11 13:52:12 +08:00

923 lines
22 KiB
C

/****************************************************************************
* apps/system/trace/trace.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <fnmatch.h>
#include <sys/ioctl.h>
#include <nuttx/note/notectl_driver.h>
#include "trace.h"
/****************************************************************************
* Private Data
****************************************************************************/
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: notectl_enable
****************************************************************************/
static bool notectl_enable(int flag, int notectlfd)
{
struct note_filter_mode_s mode;
int oldflag;
ioctl(notectlfd, NOTECTL_GETMODE, (unsigned long)&mode);
oldflag = (mode.flag & NOTE_FILTER_MODE_FLAG_ENABLE) != 0;
if (flag == oldflag)
{
/* Already set */
return false;
}
if (flag)
{
mode.flag |= NOTE_FILTER_MODE_FLAG_ENABLE;
}
else
{
mode.flag &= ~NOTE_FILTER_MODE_FLAG_ENABLE;
}
ioctl(notectlfd, NOTECTL_SETMODE, (unsigned long)&mode);
return true;
}
/****************************************************************************
* Name: trace_cmd_start
****************************************************************************/
static int trace_cmd_start(int index, int argc, FAR char **argv,
int notectlfd)
{
FAR char *endptr;
int duration = 0;
bool cont = false;
/* Usage: trace start [-c][<duration>] */
if (index < argc)
{
if (strcmp(argv[index], "-c") == 0)
{
cont = true;
index++;
}
}
if (index < argc)
{
duration = strtoul(argv[index], &endptr, 0);
if (!duration || endptr == argv[index] || *endptr != '\0')
{
fprintf(stderr,
"trace start: invalid argument '%s'\n", argv[index]);
return ERROR;
}
index++;
}
/* Clear the trace buffer */
if (!cont)
{
trace_dump_clear();
}
/* Start tracing */
notectl_enable(true, notectlfd);
if (duration > 0)
{
/* If <duration> is given, stop tracing after specified seconds. */
sleep(duration);
notectl_enable(false, notectlfd);
}
return index;
}
/****************************************************************************
* Name: trace_cmd_dump
****************************************************************************/
#ifdef CONFIG_DRIVERS_NOTERAM
static int trace_cmd_dump(int index, int argc, FAR char **argv,
int notectlfd)
{
FAR FILE *out = stdout;
bool changed = false;
bool cont = false;
int ret;
/* Usage: trace dump [-c][<filename>] */
if (index < argc)
{
if (strcmp(argv[index], "-c") == 0)
{
cont = true;
index++;
}
}
/* If <filename> is '-' or not given, trace dump is displayed
* to stdout.
*/
if (index < argc)
{
if (strcmp(argv[index], "-") != 0)
{
/* If <filename> is given, open the file stream for output. */
out = fopen(argv[index], "w");
if (out == NULL)
{
fprintf(stderr,
"trace dump: cannot open '%s'\n", argv[index]);
return ERROR;
}
}
index++;
}
/* Stop the tracing before dump */
if (!cont)
{
changed = notectl_enable(false, notectlfd);
}
/* Dump the trace header */
fputs("# tracer: nop\n#\n", out);
/* Dump the trace data */
ret = trace_dump(out);
if (changed)
{
notectl_enable(true, notectlfd);
}
/* If needed, close the file stream for dump. */
if (out != stdout)
{
fclose(out);
}
if (ret < 0)
{
fprintf(stderr,
"trace dump: dump failed\n");
return ERROR;
}
return index;
}
#endif
/****************************************************************************
* Name: trace_cmd_cmd
****************************************************************************/
#ifdef CONFIG_SYSTEM_SYSTEM
static int trace_cmd_cmd(int index, int argc, FAR char **argv, int notectlfd)
{
char command[CONFIG_NSH_LINELEN];
bool changed;
bool cont = false;
/* Usage: trace cmd [-c] <command> [<args>...] */
if (index < argc)
{
if (strcmp(argv[index], "-c") == 0)
{
cont = true;
index++;
}
}
if (index >= argc)
{
/* <command> parameter is mandatory. */
fprintf(stderr,
"trace cmd: no argument\n");
return ERROR;
}
command[0] = '\0';
while (index < argc)
{
strlcat(command, argv[index], sizeof(command));
strlcat(command, " ", sizeof(command));
index++;
}
/* Clear the trace buffer */
if (!cont)
{
trace_dump_clear();
}
/* Execute the command with tracing */
changed = notectl_enable(true, notectlfd);
system(command);
if (changed)
{
notectl_enable(false, notectlfd);
}
return index;
}
#endif
/****************************************************************************
* Name: trace_cmd_mode
****************************************************************************/
static int trace_cmd_mode(int index, int argc, FAR char **argv,
int notectlfd)
{
struct note_filter_mode_s mode;
bool owmode;
bool enable;
bool modified = false;
#ifdef CONFIG_SCHED_INSTRUMENTATION_SYSCALL
struct note_filter_syscall_s filter_syscall;
#endif
#ifdef CONFIG_SCHED_INSTRUMENTATION_IRQHANDLER
struct note_filter_irq_s filter_irq;
#endif
#if defined(CONFIG_SCHED_INSTRUMENTATION_SYSCALL) ||\
defined(CONFIG_SCHED_INSTRUMENTATION_IRQHANDLER)
int i;
int count;
#endif
/* Usage: trace mode [{+|-}{o|s|a|i}...] */
/* Get current trace mode */
ioctl(notectlfd, NOTECTL_GETMODE, (unsigned long)&mode);
owmode = trace_dump_get_overwrite();
/* Parse the mode setting parameters */
while (index < argc)
{
if (argv[index][0] != '-' && argv[index][0] != '+')
{
break;
}
enable = (argv[index][0] == '+');
switch (argv[index][1])
{
#ifdef CONFIG_DRIVERS_NOTERAM
case 'o': /* Overwrite mode */
owmode = enable;
break;
#endif
#ifdef CONFIG_SCHED_INSTRUMENTATION_SWITCH
case 'w': /* Switch trace */
if (enable)
{
mode.flag |= NOTE_FILTER_MODE_FLAG_SWITCH;
}
else
{
mode.flag &= ~NOTE_FILTER_MODE_FLAG_SWITCH;
}
break;
#endif
#ifdef CONFIG_SCHED_INSTRUMENTATION_SYSCALL
case 's': /* Syscall trace */
if (enable)
{
mode.flag |= NOTE_FILTER_MODE_FLAG_SYSCALL;
}
else
{
mode.flag &= ~NOTE_FILTER_MODE_FLAG_SYSCALL;
}
break;
case 'a': /* Record syscall arguments */
if (enable)
{
mode.flag |= NOTE_FILTER_MODE_FLAG_SYSCALL_ARGS;
}
else
{
mode.flag &= ~NOTE_FILTER_MODE_FLAG_SYSCALL_ARGS;
}
break;
#endif
#ifdef CONFIG_SCHED_INSTRUMENTATION_IRQHANDLER
case 'i': /* IRQ trace */
if (enable)
{
mode.flag |= NOTE_FILTER_MODE_FLAG_IRQ;
}
else
{
mode.flag &= ~NOTE_FILTER_MODE_FLAG_IRQ;
}
break;
#endif
#ifdef CONFIG_SCHED_INSTRUMENTATION_DUMP
case 'd': /* Dump trace */
if (enable)
{
mode.flag |= NOTE_FILTER_MODE_FLAG_DUMP;
}
else
{
mode.flag &= ~NOTE_FILTER_MODE_FLAG_DUMP;
}
break;
#endif
default:
fprintf(stderr,
"trace mode: invalid option '%s'\n", argv[index]);
return ERROR;
}
index++;
modified = true;
}
if (modified)
{
/* Update trace mode */
ioctl(notectlfd, NOTECTL_SETMODE, (unsigned long)&mode);
trace_dump_set_overwrite(owmode);
return index;
}
/* If no parameter, display current trace mode setting. */
printf("Task trace mode:\n");
printf(" Trace : %s\n",
(mode.flag & NOTE_FILTER_MODE_FLAG_ENABLE) ?
"enabled" : "disabled");
#ifdef CONFIG_DRIVERS_NOTERAM
printf(" Overwrite : %s\n",
owmode ? "on (+o)" : "off (-o)");
#endif
#ifdef CONFIG_SCHED_INSTRUMENTATION_SYSCALL
ioctl(notectlfd, NOTECTL_GETSYSCALLFILTER,
(unsigned long)&filter_syscall);
for (count = i = 0; i < SYS_nsyscalls; i++)
{
if (NOTE_FILTER_SYSCALLMASK_ISSET(i, &filter_syscall))
{
count++;
}
}
printf(" Syscall trace : %s\n",
mode.flag & NOTE_FILTER_MODE_FLAG_SYSCALL ?
"on (+s)" : "off (-s)");
if (mode.flag & NOTE_FILTER_MODE_FLAG_SYSCALL)
{
printf(" Filtered Syscalls : %d\n", count);
}
printf(" Syscall trace with args : %s\n",
mode.flag & NOTE_FILTER_MODE_FLAG_SYSCALL_ARGS ?
"on (+a)" : "off (-a)");
#endif
#ifdef CONFIG_SCHED_INSTRUMENTATION_IRQHANDLER
ioctl(notectlfd, NOTECTL_GETIRQFILTER, (unsigned long)&filter_irq);
for (count = i = 0; i < NR_IRQS; i++)
{
if (NOTE_FILTER_IRQMASK_ISSET(i, &filter_irq))
{
count++;
}
}
printf(" IRQ trace : %s\n",
mode.flag & NOTE_FILTER_MODE_FLAG_IRQ ?
"on (+i)" : "off (-i)");
if (mode.flag & NOTE_FILTER_MODE_FLAG_IRQ)
{
printf(" Filtered IRQs : %d\n", count);
}
#endif
return index;
}
/****************************************************************************
* Name: trace_cmd_switch
****************************************************************************/
#ifdef CONFIG_SCHED_INSTRUMENTATION_SWITCH
static int trace_cmd_switch(int index, int argc, FAR char **argv,
int notectlfd)
{
bool enable;
struct note_filter_mode_s mode;
/* Usage: trace switch [+|-] */
/* Get current filter setting */
ioctl(notectlfd, NOTECTL_GETMODE, (unsigned long)&mode);
/* Parse the setting parameters */
if (index < argc)
{
if (argv[index][0] == '-' || argv[index][0] == '+')
{
enable = (argv[index++][0] == '+');
if (enable ==
((mode.flag & NOTE_FILTER_MODE_FLAG_SWITCH) != 0))
{
/* Already set */
return index;
}
if (enable)
{
mode.flag |= NOTE_FILTER_MODE_FLAG_SWITCH;
}
else
{
mode.flag &= ~NOTE_FILTER_MODE_FLAG_SWITCH;
}
ioctl(notectlfd, NOTECTL_SETMODE, (unsigned long)&mode);
}
}
return index;
}
#endif
/****************************************************************************
* Name: trace_cmd_syscall
****************************************************************************/
#ifdef CONFIG_SCHED_INSTRUMENTATION_SYSCALL
static int trace_cmd_syscall(int index, int argc, FAR char **argv,
int notectlfd)
{
bool enable;
bool modified = false;
int syscallno;
FAR struct note_filter_syscall_s filter_syscall;
int n;
int count;
/* Usage: trace syscall [{+|-}<syscallname>...] */
/* Get current syscall filter setting */
ioctl(notectlfd, NOTECTL_GETSYSCALLFILTER,
(unsigned long)&filter_syscall);
/* Parse the setting parameters */
while (index < argc)
{
if (argv[index][0] != '-' && argv[index][0] != '+')
{
break;
}
enable = (argv[index][0] == '+');
/* Check whether the given pattern matches for each syscall names */
for (syscallno = 0; syscallno < SYS_nsyscalls; syscallno++)
{
if (fnmatch(&argv[index][1], g_funcnames[syscallno], 0))
{
continue;
}
/* If matches, update the masked syscall number list */
if (enable)
{
NOTE_FILTER_SYSCALLMASK_SET(syscallno, &filter_syscall);
}
else
{
NOTE_FILTER_SYSCALLMASK_CLR(syscallno, &filter_syscall);
}
}
index++;
modified = true;
}
if (modified)
{
/* Update current syscall filter setting */
ioctl(notectlfd, NOTECTL_SETSYSCALLFILTER,
(unsigned long)&filter_syscall);
}
else
{
/* If no parameter, display current setting. */
for (count = n = 0; n < SYS_nsyscalls; n++)
{
if (NOTE_FILTER_SYSCALLMASK_ISSET(n, &filter_syscall))
{
count++;
}
}
printf("Filtered Syscalls: %d\n", count);
for (n = 0; n < SYS_nsyscalls; n++)
{
if (NOTE_FILTER_SYSCALLMASK_ISSET(n, &filter_syscall))
{
printf(" %s\n", g_funcnames[n]);
}
}
}
return index;
}
#endif
/****************************************************************************
* Name: trace_cmd_irq
****************************************************************************/
#ifdef CONFIG_SCHED_INSTRUMENTATION_IRQHANDLER
static int trace_cmd_irq(int index, int argc, FAR char **argv, int notectlfd)
{
bool enable;
bool modified = false;
int irqno;
FAR char *endptr;
struct note_filter_irq_s filter_irq;
int n;
int count;
/* Usage: trace irq [{+|-}<irqnum>...] */
/* Get current irq filter setting */
ioctl(notectlfd, NOTECTL_GETIRQFILTER, (unsigned long)&filter_irq);
/* Parse the setting parameters */
while (index < argc)
{
if (argv[index][0] != '-' && argv[index][0] != '+')
{
break;
}
enable = (argv[index][0] == '+');
if (argv[index][1] == '*')
{
/* Mask or unmask all IRQs */
if (enable)
{
for (n = 0; n < NR_IRQS; n++)
{
NOTE_FILTER_IRQMASK_SET(n, &filter_irq);
}
}
else
{
NOTE_FILTER_IRQMASK_ZERO(&filter_irq);
}
}
else
{
/* Get IRQ number */
irqno = strtoul(&argv[index][1], &endptr, 0);
if (endptr == &argv[index][1] || *endptr != '\0' ||
irqno >= NR_IRQS)
{
fprintf(stderr,
"trace irq: invalid argument '%s'\n", argv[index]);
return ERROR;
}
/* Update the masked IRQ number list */
if (enable)
{
NOTE_FILTER_IRQMASK_SET(irqno, &filter_irq);
}
else
{
NOTE_FILTER_IRQMASK_CLR(irqno, &filter_irq);
}
}
index++;
modified = true;
}
if (modified)
{
/* Update current irq filter setting */
ioctl(notectlfd, NOTECTL_SETIRQFILTER, (unsigned long)&filter_irq);
}
else
{
/* If no parameter, display current setting. */
for (count = n = 0; n < NR_IRQS; n++)
{
if (NOTE_FILTER_IRQMASK_ISSET(n, &filter_irq))
{
count++;
}
}
printf("Filtered IRQs: %d\n", count);
for (n = 0; n < NR_IRQS; n++)
{
if (NOTE_FILTER_IRQMASK_ISSET(n, &filter_irq))
{
printf(" %d\n", n);
}
}
}
return index;
}
#endif
/****************************************************************************
* Name: trace_cmd_print
****************************************************************************/
#ifdef CONFIG_SCHED_INSTRUMENTATION_DUMP
static int trace_cmd_print(int index, int argc, FAR char **argv,
int notectlfd)
{
bool enable;
struct note_filter_mode_s mode;
/* Usage: trace print [+|-] */
/* Get current filter setting */
ioctl(notectlfd, NOTECTL_GETMODE, (unsigned long)&mode);
/* Parse the setting parameters */
if (index < argc)
{
if (argv[index][0] == '-' || argv[index][0] == '+')
{
enable = (argv[index++][0] == '+');
if (enable ==
((mode.flag & NOTE_FILTER_MODE_FLAG_DUMP) != 0))
{
/* Already set */
return index;
}
if (enable)
{
mode.flag |= NOTE_FILTER_MODE_FLAG_DUMP;
}
else
{
mode.flag &= ~NOTE_FILTER_MODE_FLAG_DUMP;
}
ioctl(notectlfd, NOTECTL_SETMODE, (unsigned long)&mode);
}
}
return index;
}
#endif
/****************************************************************************
* Name: show_usage
****************************************************************************/
static void show_usage(void)
{
fprintf(stderr,
"\nUsage: trace <subcommand>...\n"
"Subcommand:\n"
" start [-c][<duration>] :"
" Start task tracing\n"
" stop :"
" Stop task tracing\n"
#ifdef CONFIG_SYSTEM_SYSTEM
" cmd [-c] <command> [<args>...] :"
" Get the trace while running <command>\n"
#endif
#ifdef CONFIG_DRIVERS_NOTERAM
" dump [-a][-c][<filename>] :"
" Output the trace result\n"
" [-a] <Android SysTrace>\n"
#endif
" mode [{+|-}{o|w|s|a|i|d}...] :"
" Set task trace options\n"
#ifdef CONFIG_SCHED_INSTRUMENTATION_SWITCH
" switch [+|-] :"
" Configure switch trace filter\n"
#endif
#ifdef CONFIG_SCHED_INSTRUMENTATION_SYSCALL
" syscall [{+|-}<syscallname>...] :"
" Configure syscall trace filter\n"
#endif
#ifdef CONFIG_SCHED_INSTRUMENTATION_IRQHANDLER
" irq [{+|-}<irqnum>...] :"
" Configure IRQ trace filter\n"
#endif
#ifdef CONFIG_SCHED_INSTRUMENTATION_DUMP
" print [+|-] :"
" Configure dump trace filter\n"
#endif
);
fflush(stderr);
}
/****************************************************************************
* Public Functions
****************************************************************************/
int main(int argc, FAR char *argv[])
{
int notectlfd;
int exitcode = EXIT_FAILURE;
int i;
/* Open note control device */
notectlfd = open("/dev/notectl", 0);
if (notectlfd < 0)
{
fprintf(stderr,
"trace: cannot open /dev/notectl\n");
goto errout;
}
if (argc == 1)
{
/* No arguments - show current mode */
trace_cmd_mode(0, 0, NULL, notectlfd);
goto exit_with_close;
}
/* Parse command line arguments */
i = 1;
while (i < argc)
{
if (strcmp(argv[i], "start") == 0)
{
i = trace_cmd_start(i + 1, argc, argv, notectlfd);
}
else if (strcmp(argv[i], "stop") == 0)
{
i++;
notectl_enable(false, notectlfd);
}
#ifdef CONFIG_DRIVERS_NOTERAM
else if (strcmp(argv[i], "dump") == 0)
{
i = trace_cmd_dump(i + 1, argc, argv, notectlfd);
}
#endif
#ifdef CONFIG_SYSTEM_SYSTEM
else if (strcmp(argv[i], "cmd") == 0)
{
i = trace_cmd_cmd(i + 1, argc, argv, notectlfd);
}
#endif
else if (strcmp(argv[i], "mode") == 0)
{
i = trace_cmd_mode(i + 1, argc, argv, notectlfd);
}
#ifdef CONFIG_SCHED_INSTRUMENTATION_SWITCH
else if (strcmp(argv[i], "switch") == 0)
{
i = trace_cmd_switch(i + 1, argc, argv, notectlfd);
}
#endif
#ifdef CONFIG_SCHED_INSTRUMENTATION_SYSCALL
else if (strcmp(argv[i], "syscall") == 0)
{
i = trace_cmd_syscall(i + 1, argc, argv, notectlfd);
}
#endif
#ifdef CONFIG_SCHED_INSTRUMENTATION_IRQHANDLER
else if (strcmp(argv[i], "irq") == 0)
{
i = trace_cmd_irq(i + 1, argc, argv, notectlfd);
}
#endif
#ifdef CONFIG_SCHED_INSTRUMENTATION_DUMP
else if (strcmp(argv[i], "print") == 0)
{
i = trace_cmd_print(i + 1, argc, argv, notectlfd);
}
#endif
else
{
show_usage();
goto errout_with_close;
}
if (i < 0)
{
goto errout_with_close;
}
}
exit_with_close:
exitcode = EXIT_SUCCESS;
/* Close note */
errout_with_close:
close(notectlfd);
errout:
return exitcode;
}