373ce0a3创建于 4月20日历史提交
/****************************************************************************
 * apps/system/perf-tools/builtin-stat.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 <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <time.h>
#include <sys/ioctl.h>
#include <sys/param.h>
#include <sys/wait.h>

#include <nuttx/perf.h>
#include <nuttx/list.h>

#include "builtin.h"
#include "evlist.h"
#include "evsel.h"
#include "parse-options.h"

/****************************************************************************
 * Private Definitions
 ****************************************************************************/

#define PERF_STAT_DEFAULT_MAX_EVSTR   64

/****************************************************************************
 * Private Data
 ****************************************************************************/

static const char stat_usage[] =
  "perf stat [<options>] [<command>]";

static const struct option_s stat_options[] =
{
  OPT_STRING('a', "all-cpus", "system-wide collection from all CPUs"),
  OPT_STRING('C', "cpu <cpu>", "list of cpus to monitor in system-wide"),
  OPT_STRING('e', "event <event>",
             "event selector. use 'perf list' to list available events"),
  OPT_STRING('p', "pid <pid>", "stat events on existing process id"),
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/

static void stat_cmds_help(int sname)
{
  unsigned int i = 0;
  unsigned int longest = 0;

  printf("\n Usage: %s\n\n", stat_usage);

  for (i = 0; i < nitems(stat_options); i++)
    {
      if (longest < strlen(stat_options[i].long_name))
        {
          longest = strlen(stat_options[i].long_name);
        }
    }

  for (i = 0; i < nitems(stat_options); i++)
    {
      if (!sname || sname == stat_options[i].short_name)
        {
          printf("   -%c, --%-*s   %s\n", stat_options[i].short_name,
                                  longest, stat_options[i].long_name,
                                  stat_options[i].help);
        }
    }

  printf("\n");
}

static void print_counters(FAR struct evlist_s *evlist,
                           FAR struct timespec *ts)
{
  printf("\n Performance counter stats\n\n");

  evlist_print_counters(evlist);

  printf("\n %ld.%09ld seconds time elapsed\n", ts->tv_sec, ts->tv_nsec);
}

static int check_raw_event(FAR char *evstr)
{
  char buf[PERF_STAT_DEFAULT_MAX_EVSTR];
  int evnum = 0;
  int cnum = 0;
  int i;
  int len = strlen(evstr);

  if (len == 1)
    {
      return 0;
    }

  for (i = 0; i <= len; i++)
    {
      if (evstr[i] == ',' || evstr[i] == '\0')
        {
          if (strncmp(evstr + cnum, "r", 1))
            {
              return 0;
            }

          memset(buf, 0, sizeof(buf));
          if (i - cnum > sizeof(buf))
            {
              printf("The event name is too long, %d\n", i - cnum);
              return 0;
            }

          strncpy(buf, evstr + cnum + 1, i - cnum - 1);
          if (strlen(buf) == 0)
            {
              return 0;
            }

          if (strspn(buf, "0123456789abcdefABCDEF") != strlen(buf))
            {
              return 0;
            }

          cnum = i + 1;
          evnum++;
        }
    }

  return evnum;
}

static int check_hw_cache_event(FAR char *evstr)
{
  char buf[PERF_STAT_DEFAULT_MAX_EVSTR];
  int evnum = 0;
  int cnum = 0;
  int i;
  int len = strlen(evstr);

  for (i = 0; i <= len; i++)
    {
      if (evstr[i] == ',' || evstr[i] == '\0')
        {
          memset(buf, 0, sizeof(buf));
          if (i - cnum > sizeof(buf))
            {
              printf("The event name is too long, %d\n", i - cnum);
              return 0;
            }

          strncpy(buf, evstr + cnum, i - cnum);
          if (parse_hw_cache_events(buf, NULL) < 0)
            {
              return 0;
            }

          cnum = i + 1;
          evnum++;
        }
    }

  return evnum;
}

static int check_hw_event(FAR char *evstr)
{
  char buf[PERF_STAT_DEFAULT_MAX_EVSTR];
  int evnum = 0;
  int cnum = 0;
  int i;
  int j;
  int len = strlen(evstr);

  for (i = 0; i <= len; i++)
    {
      if (evstr[i] == ',' || evstr[i] == '\0')
        {
          memset(buf, 0, sizeof(buf));
          if (i - cnum > sizeof(buf))
            {
              printf("The event name is too long, %d\n", i - cnum);
              return 0;
            }

          strncpy(buf, evstr + cnum, i - cnum);
          for (j = 0; j < PERF_COUNT_HW_MAX; j++)
            {
              if (!strcmp(evsel_hw_names[j], buf))
                {
                  break;
                }
            }

          if (j >= PERF_COUNT_HW_MAX)
            {
              return 0;
            }

          cnum = i + 1;
          evnum++;
        }
    }

  return evnum;
}

static int check_event(FAR char *evstr, FAR int *evnum)
{
  *evnum = check_hw_event(evstr);
  if (!(*evnum))
    {
      *evnum = check_hw_cache_event(evstr);
      if (!(*evnum))
        {
          *evnum = check_raw_event(evstr);
          if (!(*evnum))
            {
              printf("Event syntax error: %s\n", evstr);
              return -EINVAL;
            }

          return PERF_TYPE_RAW;
        }
      else
        {
          return PERF_TYPE_HW_CACHE;
        }
    }
  else
    {
      return PERF_TYPE_HARDWARE;
    }
}

static int set_specified_raw_attributes(FAR char *evstr,
                      FAR struct perf_event_attr_s *attrs)
{
  char *ptr = evstr;
  int evnum = 0;

  for (ptr++; ; ptr += 2)
    {
      attrs[evnum].type = PERF_TYPE_RAW;
      attrs[evnum].config = strtol(ptr, &ptr, 16);
      attrs[evnum].size = sizeof(struct perf_event_attr_s);
      attrs[evnum].disabled = 1;
      attrs[evnum].inherit = 1;
      if (*ptr == '\0')
        {
          break;
        }

      evnum++;
    }

  return 0;
}

static int set_specified_hw_cache_attributes(FAR char *evstr,
                         FAR struct perf_event_attr_s *attrs)
{
  char buf[PERF_STAT_DEFAULT_MAX_EVSTR];
  uint64_t config;
  int evnum = 0;
  int cnum = 0;
  int i;
  int len = strlen(evstr);

  for (i = 0; i <= len; i++)
    {
      if (evstr[i] == ',' || evstr[i] == '\0')
        {
          memset(buf, 0, sizeof(buf));
          if (i - cnum > sizeof(buf))
            {
              printf("The event name is too long, %d\n", i - cnum);
              return -EINVAL;
            }

          strncpy(buf, evstr + cnum, i - cnum);
          parse_hw_cache_events(buf, &config);
          attrs[evnum].type = PERF_TYPE_HW_CACHE;
          attrs[evnum].config = config;
          attrs[evnum].size = sizeof(struct perf_event_attr_s);
          attrs[evnum].disabled = 1;
          attrs[evnum].inherit = 1;
          cnum = i + 1;
          evnum++;
        }
    }

  return 0;
}

static int set_specified_hw_attributes(FAR char *evstr,
                      FAR struct perf_event_attr_s *attrs)
{
  char buf[PERF_STAT_DEFAULT_MAX_EVSTR];
  int evnum = 0;
  int cnum = 0;
  int i;
  int j;
  int len = strlen(evstr);

  for (i = 0; i <= len; i++)
    {
      if (evstr[i] == ',' || evstr[i] == '\0')
        {
          memset(buf, 0, sizeof(buf));
          if (i - cnum > sizeof(buf))
            {
              printf("The event name is too long, %d\n", i - cnum);
              return -EINVAL;
            }

          strncpy(buf, evstr + cnum, i - cnum);
          for (j = 0; j < PERF_COUNT_HW_MAX; j++)
            {
              if (!strcmp(evsel_hw_names[j], buf))
                {
                  attrs[evnum].type = PERF_TYPE_HARDWARE;
                  attrs[evnum].config = j;
                  attrs[evnum].size = sizeof(struct perf_event_attr_s);
                  attrs[evnum].disabled = 1;
                  attrs[evnum].inherit = 1;
                  break;
                }
            }

          cnum = i + 1;
          evnum++;
        }
    }

  return 0;
}

static int add_specified_attributes(FAR struct evlist_s *evlist,
                                    FAR char *evstr)
{
  int i;
  int status;
  int evnum = 0;

  if (!evstr)
    {
      return -EINVAL;
    }

  status = check_event(evstr, &evnum);
  if (status < 0)
    {
      return -EINVAL;
    }

  evlist->attrs = zalloc(evnum * sizeof(struct perf_event_attr_s));
  if (!evlist->attrs)
    {
      printf("Zalloc attrs failed!\n");
      return -ENOMEM;
    }

  if (status == PERF_TYPE_HARDWARE)
    {
      set_specified_hw_attributes(evstr, evlist->attrs);
    }
  else if (status == PERF_TYPE_HW_CACHE)
    {
      set_specified_hw_cache_attributes(evstr, evlist->attrs);
    }
  else if (status == PERF_TYPE_RAW)
    {
      set_specified_raw_attributes(evstr, evlist->attrs);
    }

  if (evlist->system_wide)
    {
      for (i = 0; i < CONFIG_SMP_NCPUS; i++)
        {
          status = evlist_add_attrs(evlist, evlist->attrs, evnum, i);
        }
    }
  else
    {
      status = evlist_add_attrs(evlist, evlist->attrs, evnum, evlist->cpu);
    }

  return status;
}

static int set_default_attributes(FAR int *config, int nr,
                                  FAR struct perf_event_attr_s *attrs)
{
  int i;

  for (i = 0; i < nr; i++)
    {
      attrs[i].type = PERF_TYPE_HARDWARE;
      attrs[i].config = config[i];
      attrs[i].size = sizeof(struct perf_event_attr_s);
      attrs[i].disabled = 1;
      attrs[i].inherit = 1;
    }

  return 0;
}

static int add_default_attributes(FAR struct evlist_s *evlist)
{
  int i;
  int status = 0;
  int evnum = nitems(default_hw_config);

  evlist->attrs = zalloc(evnum * sizeof(struct perf_event_attr_s));
  if (!evlist->attrs)
    {
      printf("Zalloc attrs failed!\n");
      return -ENOMEM;
    }

  set_default_attributes(default_hw_config, evnum,  evlist->attrs);

  if (evlist->system_wide)
    {
      for (i = 0; i < CONFIG_SMP_NCPUS; i++)
        {
          status = evlist_add_attrs(evlist, evlist->attrs, evnum, i);
        }
    }
  else
    {
      status = evlist_add_attrs(evlist, evlist->attrs, evnum, evlist->cpu);
    }

  return status;
}

int perf_stat_handle(FAR struct evlist_s *evlist,
                    FAR struct stat_args_s *stat_args)
{
  int status = 0;
  pid_t child_pid;

  evlist_for_each_evsel(evlist, node, evsel_count_start, status);
  if (status)
    {
      return status;
    }

  if (stat_args->cmd)
    {
      status = posix_spawn(&child_pid, stat_args->cmd,
                            NULL, NULL,
                            stat_args->cmd_args, NULL);
      if (status < 0)
        {
          fprintf(stderr, "cannot create process\n");
          return status;
        }

      if (waitpid(child_pid, &status, 0) < 0)
        {
          fprintf(stderr, "waitpid error\n");
          return status;
        }
    }
  else if (stat_args->sec > 0)
    {
      sleep(stat_args->sec);
    }

  evlist_for_each_evsel(evlist, node, evsel_read_counter, status);

  evlist_for_each_evsel(evlist, node, evsel_count_end, status);

  return status;
}

static int run_perf_stat(FAR struct evlist_s *evlist,
                         FAR struct stat_args_s *stat_args)
{
  int status = 0;
  struct timespec start_time;
  struct timespec end_time;
  struct timespec run_time;

  evlist->cpu = stat_args->cpu;
  evlist->pid = stat_args->pid;

  if (stat_args->events)
    {
      evlist->defult_attrs = false;
      status = add_specified_attributes(evlist, stat_args->events);
      if (status)
        {
          stat_cmds_help(0);
          return status;
        }
    }
  else
    {
      evlist->defult_attrs = true;
      status = add_default_attributes(evlist);
      if (status)
        {
          return status;
        }
    }

  clock_gettime(CLOCK_MONOTONIC, &start_time);

  status = perf_stat_handle(evlist, stat_args);
  if (status)
    {
      return status;
    }

  clock_gettime(CLOCK_MONOTONIC, &end_time);

  run_time.tv_sec = end_time.tv_sec - start_time.tv_sec;
  run_time.tv_nsec = end_time.tv_nsec - start_time.tv_nsec;

  print_counters(evlist, &run_time);

  return status;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

int cmd_stat(int argc, FAR const char **argv)
{
  FAR static struct evlist_s *evlist;
  int status = 0;
  struct stat_args_s stat_args;

  memset(&stat_args, 0, sizeof(struct stat_args_s));

  status = parse_stat_options(argc, (char **)argv, &stat_args);
  if (status || stat_args.type == STAT_ARGS_HELP)
    {
      stat_cmds_help(0);
      return status;
    }

  evlist = evlist_new();
  if (evlist == NULL)
    {
      return -ENOMEM;
    }

  evlist->sec = stat_args.sec;
  evlist->type = stat_args.type;
  if (stat_args.pid == -1 && stat_args.cpu == -1)
    {
      evlist->system_wide = true;
    }
  else
    {
      evlist->system_wide = false;
    }

  status = run_perf_stat(evlist, &stat_args);

  evlist_delete(evlist);

  return status;
}