Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  intel-tpebs.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-only
/*
 * intel_tpebs.c: Intel TPEBS support
 */


#include <api/fs/fs.h>
#include <sys/param.h>
#include <subcmd/run-command.h>
#include <thread.h>
#include "intel-tpebs.h"
#include <linux/list.h>
#include <linux/zalloc.h>
#include <linux/err.h>
#include "sample.h"
#include "counts.h"
#include "debug.h"
#include "evlist.h"
#include "evsel.h"
#include "mutex.h"
#include "session.h"
#include "stat.h"
#include "tool.h"
#include "cpumap.h"
#include "metricgroup.h"
#include "stat.h"
#include <sys/stat.h>
#include <sys/file.h>
#include <poll.h>
#include <math.h>

#define PERF_DATA  "-"

bool tpebs_recording;
enum tpebs_mode tpebs_mode;
static LIST_HEAD(tpebs_results);
static pthread_t tpebs_reader_thread;
static struct child_process tpebs_cmd;
static int control_fd[2], ack_fd[2];
static struct mutex tpebs_mtx;

struct tpebs_retire_lat {
 struct list_head nd;
 /** @evsel: The evsel that opened the retire_lat event. */
 struct evsel *evsel;
 /** @event: Event passed to perf record. */
 char *event;
 /** @stats: Recorded retirement latency stats. */
 struct stats stats;
 /** @last: Last retirement latency read. */
 uint64_t last;
 /* Has the event been sent to perf record? */
 bool started;
};

static void tpebs_mtx_init(void)
{
 mutex_init(&tpebs_mtx);
}

static struct mutex *tpebs_mtx_get(void)
{
 static pthread_once_t tpebs_mtx_once = PTHREAD_ONCE_INIT;

 pthread_once(&tpebs_mtx_once, tpebs_mtx_init);
 return &tpebs_mtx;
}

static struct tpebs_retire_lat *tpebs_retire_lat__find(struct evsel *evsel)
 EXCLUSIVE_LOCKS_REQUIRED(tpebs_mtx_get());

static int evsel__tpebs_start_perf_record(struct evsel *evsel)
{
 const char **record_argv;
 int tpebs_event_size = 0, i = 0, ret;
 char control_fd_buf[32];
 char cpumap_buf[50];
 struct tpebs_retire_lat *t;

 list_for_each_entry(t, &tpebs_results, nd)
  tpebs_event_size++;

 record_argv = malloc((10 + 2 * tpebs_event_size) * sizeof(*record_argv));
 if (!record_argv)
  return -ENOMEM;

 record_argv[i++] = "perf";
 record_argv[i++] = "record";
 record_argv[i++] = "-W";
 record_argv[i++] = "--synth=no";

 scnprintf(control_fd_buf, sizeof(control_fd_buf), "--control=fd:%d,%d",
    control_fd[0], ack_fd[1]);
 record_argv[i++] = control_fd_buf;

 record_argv[i++] = "-o";
 record_argv[i++] = PERF_DATA;

 if (!perf_cpu_map__is_any_cpu_or_is_empty(evsel->evlist->core.user_requested_cpus)) {
  cpu_map__snprint(evsel->evlist->core.user_requested_cpus, cpumap_buf,
     sizeof(cpumap_buf));
  record_argv[i++] = "-C";
  record_argv[i++] = cpumap_buf;
 }

 list_for_each_entry(t, &tpebs_results, nd) {
  record_argv[i++] = "-e";
  record_argv[i++] = t->event;
 }
 record_argv[i++] = NULL;
 assert(i == 10 + 2 * tpebs_event_size || i == 8 + 2 * tpebs_event_size);
 /* Note, no workload given so system wide is implied. */

 assert(tpebs_cmd.pid == 0);
 tpebs_cmd.argv = record_argv;
 tpebs_cmd.out = -1;
 ret = start_command(&tpebs_cmd);
 zfree(&tpebs_cmd.argv);
 list_for_each_entry(t, &tpebs_results, nd)
  t->started = true;

 return ret;
}

static bool is_child_pid(pid_t parent, pid_t child)
{
 if (parent < 0 || child < 0)
  return false;

 while (true) {
  char path[PATH_MAX];
  char line[256];
  FILE *fp;

new_child:
  if (parent == child)
   return true;

  if (child <= 0)
   return false;

  scnprintf(path, sizeof(path), "%s/%d/status", procfs__mountpoint(), child);
  fp = fopen(path, "r");
  if (!fp) {
   /* Presumably the process went away. Assume not a child. */
   return false;
  }
  while (fgets(line, sizeof(line), fp) != NULL) {
   if (strncmp(line, "PPid:", 5) == 0) {
    fclose(fp);
    if (sscanf(line + 5, "%d", &child) != 1) {
     /* Unexpected error parsing. */
     return false;
    }
    goto new_child;
   }
  }
  /* Unexpected EOF. */
  fclose(fp);
  return false;
 }
}

static bool should_ignore_sample(const struct perf_sample *sample, const struct tpebs_retire_lat *t)
{
 pid_t workload_pid, sample_pid = sample->pid;

 /*
 * During evlist__purge the evlist will be removed prior to the
 * evsel__exit calling evsel__tpebs_close and taking the
 * tpebs_mtx. Avoid a segfault by ignoring samples in this case.
 */

 if (t->evsel->evlist == NULL)
  return true;

 workload_pid = t->evsel->evlist->workload.pid;
 if (workload_pid < 0 || workload_pid == sample_pid)
  return false;

 if (!t->evsel->core.attr.inherit)
  return true;

 return !is_child_pid(workload_pid, sample_pid);
}

static int process_sample_event(const struct perf_tool *tool __maybe_unused,
    union perf_event *event __maybe_unused,
    struct perf_sample *sample,
    struct evsel *evsel,
    struct machine *machine __maybe_unused)
{
 struct tpebs_retire_lat *t;

 mutex_lock(tpebs_mtx_get());
 if (tpebs_cmd.pid == 0) {
  /* Record has terminated. */
  mutex_unlock(tpebs_mtx_get());
  return 0;
 }
 t = tpebs_retire_lat__find(evsel);
 if (!t) {
  mutex_unlock(tpebs_mtx_get());
  return -EINVAL;
 }
 if (should_ignore_sample(sample, t)) {
  mutex_unlock(tpebs_mtx_get());
  return 0;
 }
 /*
 * Need to handle per core results? We are assuming average retire
 * latency value will be used. Save the number of samples and the sum of
 * retire latency value for each event.
 */

 t->last = sample->weight3;
 update_stats(&t->stats, sample->weight3);
 mutex_unlock(tpebs_mtx_get());
 return 0;
}

static int process_feature_event(struct perf_session *session,
     union perf_event *event)
{
 if (event->feat.feat_id < HEADER_LAST_FEATURE)
  return perf_event__process_feature(session, event);
 return 0;
}

static void *__sample_reader(void *arg __maybe_unused)
{
 struct perf_session *session;
 struct perf_data data = {
  .mode = PERF_DATA_MODE_READ,
  .path = PERF_DATA,
  .file.fd = tpebs_cmd.out,
 };
 struct perf_tool tool;

 perf_tool__init(&tool, /*ordered_events=*/false);
 tool.sample = process_sample_event;
 tool.feature = process_feature_event;
 tool.attr = perf_event__process_attr;

 session = perf_session__new(&data, &tool);
 if (IS_ERR(session))
  return NULL;
 perf_session__process_events(session);
 perf_session__delete(session);

 return NULL;
}

static int tpebs_send_record_cmd(const char *msg) EXCLUSIVE_LOCKS_REQUIRED(tpebs_mtx_get())
{
 struct pollfd pollfd = { .events = POLLIN, };
 int ret, len, retries = 0;
 char ack_buf[8];

 /* Check if the command exited before the send, done with the lock held. */
 if (tpebs_cmd.pid == 0)
  return 0;

 /*
 * Let go of the lock while sending/receiving as blocking can starve the
 * sample reading thread.
 */

 mutex_unlock(tpebs_mtx_get());

 /* Send perf record command.*/
 len = strlen(msg);
 ret = write(control_fd[1], msg, len);
 if (ret != len) {
  pr_err("perf record control write control message '%s' failed\n", msg);
  ret = -EPIPE;
  goto out;
 }

 if (!strcmp(msg, EVLIST_CTL_CMD_STOP_TAG)) {
  ret = 0;
  goto out;
 }

 /* Wait for an ack. */
 pollfd.fd = ack_fd[0];

 /*
 * We need this poll to ensure the ack_fd PIPE will not hang
 * when perf record failed for any reason. The timeout value
 * 3000ms is an empirical selection.
 */

again:
 if (!poll(&pollfd, 1, 500)) {
  if (check_if_command_finished(&tpebs_cmd)) {
   ret = 0;
   goto out;
  }

  if (retries++ < 6)
   goto again;
  pr_err("tpebs failed: perf record ack timeout for '%s'\n", msg);
  ret = -ETIMEDOUT;
  goto out;
 }

 if (!(pollfd.revents & POLLIN)) {
  if (check_if_command_finished(&tpebs_cmd)) {
   ret = 0;
   goto out;
  }

  pr_err("tpebs failed: did not received an ack for '%s'\n", msg);
  ret = -EPIPE;
  goto out;
 }

 ret = read(ack_fd[0], ack_buf, sizeof(ack_buf));
 if (ret > 0)
  ret = strcmp(ack_buf, EVLIST_CTL_CMD_ACK_TAG);
 else
  pr_err("tpebs: perf record control ack failed\n");
out:
 /* Re-take lock as expected by caller. */
 mutex_lock(tpebs_mtx_get());
 return ret;
}

/*
 * tpebs_stop - stop the sample data read thread and the perf record process.
 */

static int tpebs_stop(void) EXCLUSIVE_LOCKS_REQUIRED(tpebs_mtx_get())
{
 int ret = 0;

 /* Like tpebs_start, we should only run tpebs_end once. */
 if (tpebs_cmd.pid != 0) {
  tpebs_send_record_cmd(EVLIST_CTL_CMD_STOP_TAG);
  tpebs_cmd.pid = 0;
  mutex_unlock(tpebs_mtx_get());
  pthread_join(tpebs_reader_thread, NULL);
  mutex_lock(tpebs_mtx_get());
  close(control_fd[0]);
  close(control_fd[1]);
  close(ack_fd[0]);
  close(ack_fd[1]);
  close(tpebs_cmd.out);
  ret = finish_command(&tpebs_cmd);
  tpebs_cmd.pid = 0;
  if (ret == -ERR_RUN_COMMAND_WAITPID_SIGNAL)
   ret = 0;
 }
 return ret;
}

/**
 * evsel__tpebs_event() - Create string event encoding to pass to `perf record`.
 */

static int evsel__tpebs_event(struct evsel *evsel, char **event)
{
 char *name, *modifier;
 int ret;

 name = strdup(evsel->name);
 if (!name)
  return -ENOMEM;

 modifier = strrchr(name, 'R');
 if (!modifier) {
  ret = -EINVAL;
  goto out;
 }
 *modifier = 'p';
 modifier = strchr(name, ':');
 if (!modifier)
  modifier = strrchr(name, '/');
 if (!modifier) {
  ret = -EINVAL;
  goto out;
 }
 *modifier = '\0';
 if (asprintf(event, "%s/name=tpebs_event_%p/%s", name, evsel, modifier + 1) > 0)
  ret = 0;
 else
  ret = -ENOMEM;
out:
 if (ret)
  pr_err("Tpebs event modifier broken '%s'\n", evsel->name);
 free(name);
 return ret;
}

static struct tpebs_retire_lat *tpebs_retire_lat__new(struct evsel *evsel)
{
 struct tpebs_retire_lat *result = zalloc(sizeof(*result));
 int ret;

 if (!result)
  return NULL;

 ret = evsel__tpebs_event(evsel, &result->event);
 if (ret) {
  free(result);
  return NULL;
 }
 result->evsel = evsel;
 return result;
}

static void tpebs_retire_lat__delete(struct tpebs_retire_lat *r)
{
 zfree(&r->event);
 free(r);
}

static struct tpebs_retire_lat *tpebs_retire_lat__find(struct evsel *evsel)
{
 struct tpebs_retire_lat *t;
 unsigned long num;
 const char *evsel_name;

 /*
 * Evsels will match for evlist with the retirement latency event. The
 * name with "tpebs_event_" prefix will be present on events being read
 * from `perf record`.
 */

 if (evsel__is_retire_lat(evsel)) {
  list_for_each_entry(t, &tpebs_results, nd) {
   if (t->evsel == evsel)
    return t;
  }
  return NULL;
 }
 evsel_name = strstr(evsel->name, "tpebs_event_");
 if (!evsel_name) {
  /* Unexpected that the perf record should have other events. */
  return NULL;
 }
 errno = 0;
 num = strtoull(evsel_name + 12, NULL, 16);
 if (errno) {
  pr_err("Bad evsel for tpebs find '%s'\n", evsel->name);
  return NULL;
 }
 list_for_each_entry(t, &tpebs_results, nd) {
  if ((unsigned long)t->evsel == num)
   return t;
 }
 return NULL;
}

/**
 * evsel__tpebs_prepare - create tpebs data structures ready for opening.
 * @evsel: retire_latency evsel, all evsels on its list will be prepared.
 */

static int evsel__tpebs_prepare(struct evsel *evsel)
{
 struct evsel *pos;
 struct tpebs_retire_lat *tpebs_event;

 mutex_lock(tpebs_mtx_get());
 tpebs_event = tpebs_retire_lat__find(evsel);
 if (tpebs_event) {
  /* evsel, or an identically named one, was already prepared. */
  mutex_unlock(tpebs_mtx_get());
  return 0;
 }
 tpebs_event = tpebs_retire_lat__new(evsel);
 if (!tpebs_event) {
  mutex_unlock(tpebs_mtx_get());
  return -ENOMEM;
 }
 list_add_tail(&tpebs_event->nd, &tpebs_results);
 mutex_unlock(tpebs_mtx_get());

 /*
 * Eagerly prepare all other evsels on the list to try to ensure that by
 * open they are all known.
 */

 evlist__for_each_entry(evsel->evlist, pos) {
  int ret;

  if (pos == evsel || !pos->retire_lat)
   continue;

  ret = evsel__tpebs_prepare(pos);
  if (ret)
   return ret;
 }
 return 0;
}

/**
 * evsel__tpebs_open - starts tpebs execution.
 * @evsel: retire_latency evsel, all evsels on its list will be selected. Each
 *         evsel is sampled to get the average retire_latency value.
 */

int evsel__tpebs_open(struct evsel *evsel)
{
 int ret;
 bool tpebs_empty;

 /* We should only run tpebs_start when tpebs_recording is enabled. */
 if (!tpebs_recording)
  return 0;
 /* Only start the events once. */
 if (tpebs_cmd.pid != 0) {
  struct tpebs_retire_lat *t;
  bool valid;

  mutex_lock(tpebs_mtx_get());
  t = tpebs_retire_lat__find(evsel);
  valid = t && t->started;
  mutex_unlock(tpebs_mtx_get());
  /* May fail as the event wasn't started. */
  return valid ? 0 : -EBUSY;
 }

 ret = evsel__tpebs_prepare(evsel);
 if (ret)
  return ret;

 mutex_lock(tpebs_mtx_get());
 tpebs_empty = list_empty(&tpebs_results);
 if (!tpebs_empty) {
  /*Create control and ack fd for --control*/
  if (pipe(control_fd) < 0) {
   pr_err("tpebs: Failed to create control fifo");
   ret = -1;
   goto out;
  }
  if (pipe(ack_fd) < 0) {
   pr_err("tpebs: Failed to create control fifo");
   ret = -1;
   goto out;
  }

  ret = evsel__tpebs_start_perf_record(evsel);
  if (ret)
   goto out;

  if (pthread_create(&tpebs_reader_thread, /*attr=*/NULL, __sample_reader,
       /*arg=*/NULL)) {
   kill(tpebs_cmd.pid, SIGTERM);
   close(tpebs_cmd.out);
   pr_err("Could not create thread to process sample data.\n");
   ret = -1;
   goto out;
  }
  ret = tpebs_send_record_cmd(EVLIST_CTL_CMD_ENABLE_TAG);
 }
out:
 if (ret) {
  struct tpebs_retire_lat *t = tpebs_retire_lat__find(evsel);

  list_del_init(&t->nd);
  tpebs_retire_lat__delete(t);
 }
 mutex_unlock(tpebs_mtx_get());
 return ret;
}

int evsel__tpebs_read(struct evsel *evsel, int cpu_map_idx, int thread)
{
 struct perf_counts_values *count, *old_count = NULL;
 struct tpebs_retire_lat *t;
 uint64_t val;
 int ret;

 /* Only set retire_latency value to the first CPU and thread. */
 if (cpu_map_idx != 0 || thread != 0)
  return 0;

 if (evsel->prev_raw_counts)
  old_count = perf_counts(evsel->prev_raw_counts, cpu_map_idx, thread);

 count = perf_counts(evsel->counts, cpu_map_idx, thread);

 mutex_lock(tpebs_mtx_get());
 t = tpebs_retire_lat__find(evsel);
 /*
 * If reading the first tpebs result, send a ping to the record
 * process. Allow the sample reader a chance to read by releasing and
 * reacquiring the lock.
 */

 if (t && &t->nd == tpebs_results.next) {
  ret = tpebs_send_record_cmd(EVLIST_CTL_CMD_PING_TAG);
  mutex_unlock(tpebs_mtx_get());
  if (ret)
   return ret;
  mutex_lock(tpebs_mtx_get());
 }
 if (t == NULL || t->stats.n == 0) {
  /* No sample data, use default. */
  if (tpebs_recording) {
   pr_warning_once(
    "Using precomputed retirement latency data as no samples\n");
  }
  val = 0;
  switch (tpebs_mode) {
  case TPEBS_MODE__MIN:
   val = rint(evsel->retirement_latency.min);
   break;
  case TPEBS_MODE__MAX:
   val = rint(evsel->retirement_latency.max);
   break;
  default:
  case TPEBS_MODE__LAST:
  case TPEBS_MODE__MEAN:
   val = rint(evsel->retirement_latency.mean);
   break;
  }
 } else {
  switch (tpebs_mode) {
  case TPEBS_MODE__MIN:
   val = t->stats.min;
   break;
  case TPEBS_MODE__MAX:
   val = t->stats.max;
   break;
  case TPEBS_MODE__LAST:
   val = t->last;
   break;
  default:
  case TPEBS_MODE__MEAN:
   val = rint(t->stats.mean);
   break;
  }
 }
 mutex_unlock(tpebs_mtx_get());

 if (old_count) {
  count->val = old_count->val + val;
  count->run = old_count->run + 1;
  count->ena = old_count->ena + 1;
 } else {
  count->val = val;
  count->run++;
  count->ena++;
 }
 return 0;
}

/**
 * evsel__tpebs_close() - delete tpebs related data. If the last event, stop the
 * created thread and process by calling tpebs_stop().
 *
 * This function is called in evsel__close() to be symmetric with
 * evsel__tpebs_open() being called in evsel__open().
 */

void evsel__tpebs_close(struct evsel *evsel)
{
 struct tpebs_retire_lat *t;

 mutex_lock(tpebs_mtx_get());
 t = tpebs_retire_lat__find(evsel);
 if (t) {
  list_del_init(&t->nd);
  tpebs_retire_lat__delete(t);

  if (list_empty(&tpebs_results))
   tpebs_stop();
 }
 mutex_unlock(tpebs_mtx_get());
}

Messung V0.5
C=94 H=94 G=93

¤ Dauer der Verarbeitung: 0.5 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge