// SPDX-License-Identifier: GPL-2.0/* * builtin-diff.c * * Builtin diff command: Analyze two perf.data input files, look up and read * DSOs and symbol information, sort them and produce a diff. */#include "builtin.h" #include "util/debug.h"#include "util/event.h"#include "util/hist.h"#include "util/evsel.h"#include "util/evlist.h"#include "util/session.h"#include "util/tool.h"#include "util/sort.h"#include "util/symbol.h"#include "util/util.h"#include "util/data.h"#include "util/config.h"#include "util/time-utils.h" #include <errno.h>#include <inttypes.h>#include <stdlib.h>#include <math.h> struct perf_diff { struct perf_tool tool; const char *time_str; struct perf_time_interval *ptime_range; int range_size; int range_num;}; /* Diff command specific HPP columns. */enum { PERF_HPP_DIFF__BASELINE, PERF_HPP_DIFF__PERIOD, PERF_HPP_DIFF__PERIOD_BASELINE, PERF_HPP_DIFF__DELTA, PERF_HPP_DIFF__RATIO, PERF_HPP_DIFF__WEIGHTED_DIFF, PERF_HPP_DIFF__FORMULA, PERF_HPP_DIFF__DELTA_ABS, PERF_HPP_DIFF__MAX_INDEX}; struct diff_hpp_fmt { struct perf_hpp_fmt fmt; int idx; char *header; int header_width;}; struct data__file { struct perf_session *session; struct perf_data data; int idx; struct hists *hists; struct diff_hpp_fmt fmt[PERF_HPP_DIFF__MAX_INDEX];}; static struct data__file *data__files;static int data__files_cnt; #define data__for_each_file_start(i, d, s) \ for (i = s, d = &data__files[s]; \ i < data__files_cnt; \ i++, d = &data__files[i]) #define data__for_each_file(i, d) data__for_each_file_start(i, d, 0)#define data__for_each_file_new(i, d) data__for_each_file_start(i, d, 1) static bool force;static bool show_period;static bool show_formula;static bool show_baseline_only;static unsigned int sort_compute = 1; static s64 compute_wdiff_w1;static s64 compute_wdiff_w2; static const char *cpu_list;static DECLARE_BITMAP(cpu_bitmap, MAX_NR_CPUS); enum { COMPUTE_DELTA, COMPUTE_RATIO, COMPUTE_WEIGHTED_DIFF, COMPUTE_DELTA_ABS, COMPUTE_MAX,}; const char *compute_names[COMPUTE_MAX] = { [COMPUTE_DELTA] = "delta", [COMPUTE_DELTA_ABS] = "delta-abs", [COMPUTE_RATIO] = "ratio", [COMPUTE_WEIGHTED_DIFF] = "wdiff",}; static int compute = COMPUTE_DELTA_ABS; static int compute_2_hpp[COMPUTE_MAX] = { [COMPUTE_DELTA] = PERF_HPP_DIFF__DELTA, [COMPUTE_DELTA_ABS] = PERF_HPP_DIFF__DELTA_ABS, [COMPUTE_RATIO] = PERF_HPP_DIFF__RATIO, [COMPUTE_WEIGHTED_DIFF] = PERF_HPP_DIFF__WEIGHTED_DIFF,}; #define MAX_COL_WIDTH 70 static struct header_column { const char *name; int width;} columns[PERF_HPP_DIFF__MAX_INDEX] = { [PERF_HPP_DIFF__BASELINE] = { .name = "Baseline", }, [PERF_HPP_DIFF__PERIOD] = { .name = "Period", .width = 14, }, [PERF_HPP_DIFF__PERIOD_BASELINE] = { .name = "Base period", .width = 14, }, [PERF_HPP_DIFF__DELTA] = { .name = "Delta", .width = 7, }, [PERF_HPP_DIFF__DELTA_ABS] = { .name = "Delta Abs", .width = 7, }, [PERF_HPP_DIFF__RATIO] = { .name = "Ratio", .width = 14, }, [PERF_HPP_DIFF__WEIGHTED_DIFF] = { .name = "Weighted diff", .width = 14, }, [PERF_HPP_DIFF__FORMULA] = { .name = "Formula", .width = MAX_COL_WIDTH, }}; static int setup_compute_opt_wdiff(char *opt){ char *w1_str = opt; char *w2_str; int ret = -EINVAL; if (!opt) goto out; w2_str = strchr(opt, ','); if (!w2_str) goto out; *w2_str++ = 0x0; if (!*w2_str) goto out; compute_wdiff_w1 = strtol(w1_str, NULL, 10); compute_wdiff_w2 = strtol(w2_str, NULL, 10); if (!compute_wdiff_w1 || !compute_wdiff_w2) goto out; pr_debug("compute wdiff w1(%" PRId64 ") w2(%" PRId64 ")\n", compute_wdiff_w1, compute_wdiff_w2); ret = 0; out: if (ret) pr_err("Failed: wrong weight data, use 'wdiff:w1,w2'\n"); return ret;} static int setup_compute_opt(char *opt){ if (compute == COMPUTE_WEIGHTED_DIFF) return setup_compute_opt_wdiff(opt); if (opt) { pr_err("Failed: extra option specified '%s'", opt); return -EINVAL; } return 0;} static int setup_compute(const struct option *opt, const char *str, int unset __maybe_unused){ int *cp = (int *) opt->value; char *cstr = (char *) str; char buf[50]; unsigned i; char *option; if (!str) { *cp = COMPUTE_DELTA; return 0; } option = strchr(str, ':'); if (option) { unsigned len = option++ - str; /* * The str data are not writeable, so we need * to use another buffer. */ /* No option value is longer. */ if (len >= sizeof(buf)) return -EINVAL; strncpy(buf, str, len); buf[len] = 0x0; cstr = buf; } for (i = 0; i < COMPUTE_MAX; i++) if (!strcmp(cstr, compute_names[i])) { *cp = i; return setup_compute_opt(option); } pr_err("Failed: '%s' is not computation method " "(use 'delta','ratio' or 'wdiff')\n", str); return -EINVAL;} static double period_percent(struct hist_entry *he, u64 period){ u64 total = hists__total_period(he->hists); return (period * 100.0) / total;} static double compute_delta(struct hist_entry *he, struct hist_entry *pair){ double old_percent = period_percent(he, he->stat.period); double new_percent = period_percent(pair, pair->stat.period); pair->diff.period_ratio_delta = new_percent - old_percent; pair->diff.computed = true; return pair->diff.period_ratio_delta;} static double compute_ratio(struct hist_entry *he, struct hist_entry *pair){ double old_period = he->stat.period ?: 1; double new_period = pair->stat.period; pair->diff.computed = true; pair->diff.period_ratio = new_period / old_period; return pair->diff.period_ratio;} static s64 compute_wdiff(struct hist_entry *he, struct hist_entry *pair){ u64 old_period = he->stat.period; u64 new_period = pair->stat.period; pair->diff.computed = true; pair->diff.wdiff = new_period * compute_wdiff_w2 - old_period * compute_wdiff_w1; return pair->diff.wdiff;} static int formula_delta(struct hist_entry *he, struct hist_entry *pair, char *buf, size_t size){ u64 he_total = he->hists->stats.total_period; u64 pair_total = pair->hists->stats.total_period; if (symbol_conf.filter_relative) { he_total = he->hists->stats.total_non_filtered_period; pair_total = pair->hists->stats.total_non_filtered_period; } return scnprintf(buf, size, "(%" PRIu64 " * 100 / %" PRIu64 ") - " "(%" PRIu64 " * 100 / %" PRIu64 ")", pair->stat.period, pair_total, he->stat.period, he_total);} static int formula_ratio(struct hist_entry *he, struct hist_entry *pair, char *buf, size_t size){ double old_period = he->stat.period; double new_period = pair->stat.period; return scnprintf(buf, size, "%.0F / %.0F", new_period, old_period);} static int formula_wdiff(struct hist_entry *he, struct hist_entry *pair, char *buf, size_t size){ u64 old_period = he->stat.period; u64 new_period = pair->stat.period; return scnprintf(buf, size, "(%" PRIu64 " * " "%" PRId64 ") - (%" PRIu64 " * " "%" PRId64 ")", new_period, compute_wdiff_w2, old_period, compute_wdiff_w1);} static int formula_fprintf(struct hist_entry *he, struct hist_entry *pair, char *buf, size_t size){ switch (compute) { case COMPUTE_DELTA: case COMPUTE_DELTA_ABS: return formula_delta(he, pair, buf, size); case COMPUTE_RATIO: return formula_ratio(he, pair, buf, size); case COMPUTE_WEIGHTED_DIFF: return formula_wdiff(he, pair, buf, size); default: BUG_ON(1); } return -1;} static int diff__process_sample_event(struct perf_tool *tool, union perf_event *event, struct perf_sample *sample, struct perf_evsel *evsel, struct machine *machine){ struct perf_diff *pdiff = container_of(tool, struct perf_diff, tool); struct addr_location al; struct hists *hists = evsel__hists(evsel); int ret = -1; if (perf_time__ranges_skip_sample(pdiff->ptime_range, pdiff->range_num, sample->time)) { return 0; } if (machine__resolve(machine, &al, sample) < 0) { pr_warning("problem processing %d event, skipping it.\n", event->header.type); return -1; } if (cpu_list && !test_bit(sample->cpu, cpu_bitmap)) { ret = 0; goto out_put; } if (!hists__add_entry(hists, &al, NULL, NULL, NULL, sample, true)) { pr_warning("problem incrementing symbol period, skipping event\n"); goto out_put; } /* * The total_period is updated here before going to the output * tree since normally only the baseline hists will call * hists__output_resort() and precompute needs the total * period in order to sort entries by percentage delta. */ hists->stats.total_period += sample->period; if (!al.filtered) hists->stats.total_non_filtered_period += sample->period; ret = 0;out_put: addr_location__put(&al); return ret;} static struct perf_diff pdiff = { .tool = { .sample = diff__process_sample_event, .mmap = perf_event__process_mmap, .mmap2 = perf_event__process_mmap2, .comm = perf_event__process_comm, .exit = perf_event__process_exit, .fork = perf_event__process_fork, .lost = perf_event__process_lost, .namespaces = perf_event__process_namespaces, .ordered_events = true, .ordering_requires_timestamps = true, },}; static struct perf_evsel *evsel_match(struct perf_evsel *evsel, struct perf_evlist *evlist){ struct perf_evsel *e; evlist__for_each_entry(evlist, e) { if (perf_evsel__match2(evsel, e)) return e; } return NULL;} static void perf_evlist__collapse_resort(struct perf_evlist *evlist){ struct perf_evsel *evsel; evlist__for_each_entry(evlist, evsel) { struct hists *hists = evsel__hists(evsel); hists__collapse_resort(hists, NULL); }} static struct data__file *fmt_to_data_file(struct perf_hpp_fmt *fmt){ struct diff_hpp_fmt *dfmt = container_of(fmt, struct diff_hpp_fmt, fmt); void *ptr = dfmt - dfmt->idx; struct data__file *d = container_of(ptr, struct data__file, fmt); return d;} static struct hist_entry*get_pair_data(struct hist_entry *he, struct data__file *d){ if (hist_entry__has_pairs(he)) { struct hist_entry *pair; list_for_each_entry(pair, &he->pairs.head, pairs.node) if (pair->hists == d->hists) return pair; } return NULL;} static struct hist_entry*get_pair_fmt(struct hist_entry *he, struct diff_hpp_fmt *dfmt){ struct data__file *d = fmt_to_data_file(&dfmt->fmt); return get_pair_data(he, d);} static void hists__baseline_only(struct hists *hists){ struct rb_root_cached *root; struct rb_node *next; if (hists__has(hists, need_collapse)) root = &hists->entries_collapsed; else root = hists->entries_in; next = rb_first_cached(root); while (next != NULL) { struct hist_entry *he = rb_entry(next, struct hist_entry, rb_node_in); next = rb_next(&he->rb_node_in); if (!hist_entry__next_pair(he)) { rb_erase_cached(&he->rb_node_in, root); hist_entry__delete(he); } }} static void hists__precompute(struct hists *hists){ struct rb_root_cached *root; struct rb_node *next; if (hists__has(hists, need_collapse)) root = &hists->entries_collapsed; else root = hists->entries_in; next = rb_first_cached(root); while (next != NULL) { struct hist_entry *he, *pair; struct data__file *d; int i; he = rb_entry(next, struct hist_entry, rb_node_in); next = rb_next(&he->rb_node_in); data__for_each_file_new(i, d) { pair = get_pair_data(he, d); if (!pair) continue; Lines 1–500 of 1503. Load more
// SPDX-License-Identifier: GPL-2.0 /* * builtin-diff.c * * Builtin diff command: Analyze two perf.data input files, look up and read * DSOs and symbol information, sort them and produce a diff. */ #include "builtin.h" #include "util/debug.h" #include "util/event.h" #include "util/hist.h" #include "util/evsel.h" #include "util/evlist.h" #include "util/session.h" #include "util/tool.h" #include "util/sort.h" #include "util/symbol.h" #include "util/util.h" #include "util/data.h" #include "util/config.h" #include "util/time-utils.h" #include <errno.h> #include <inttypes.h> #include <stdlib.h> #include <math.h> struct perf_diff { struct perf_tool tool; const char *time_str; struct perf_time_interval *ptime_range; int range_size; int range_num; }; /* Diff command specific HPP columns. */ enum { PERF_HPP_DIFF__BASELINE, PERF_HPP_DIFF__PERIOD, PERF_HPP_DIFF__PERIOD_BASELINE, PERF_HPP_DIFF__DELTA, PERF_HPP_DIFF__RATIO, PERF_HPP_DIFF__WEIGHTED_DIFF, PERF_HPP_DIFF__FORMULA, PERF_HPP_DIFF__DELTA_ABS, PERF_HPP_DIFF__MAX_INDEX }; struct diff_hpp_fmt { struct perf_hpp_fmt fmt; int idx; char *header; int header_width; }; struct data__file { struct perf_session *session; struct perf_data data; int idx; struct hists *hists; struct diff_hpp_fmt fmt[PERF_HPP_DIFF__MAX_INDEX]; }; static struct data__file *data__files; static int data__files_cnt; #define data__for_each_file_start(i, d, s) \ for (i = s, d = &data__files[s]; \ i < data__files_cnt; \ i++, d = &data__files[i]) #define data__for_each_file(i, d) data__for_each_file_start(i, d, 0) #define data__for_each_file_new(i, d) data__for_each_file_start(i, d, 1) static bool force; static bool show_period; static bool show_formula; static bool show_baseline_only; static unsigned int sort_compute = 1; static s64 compute_wdiff_w1; static s64 compute_wdiff_w2; static const char *cpu_list; static DECLARE_BITMAP(cpu_bitmap, MAX_NR_CPUS); enum { COMPUTE_DELTA, COMPUTE_RATIO, COMPUTE_WEIGHTED_DIFF, COMPUTE_DELTA_ABS, COMPUTE_MAX, }; const char *compute_names[COMPUTE_MAX] = { [COMPUTE_DELTA] = "delta", [COMPUTE_DELTA_ABS] = "delta-abs", [COMPUTE_RATIO] = "ratio", [COMPUTE_WEIGHTED_DIFF] = "wdiff", }; static int compute = COMPUTE_DELTA_ABS; static int compute_2_hpp[COMPUTE_MAX] = { [COMPUTE_DELTA] = PERF_HPP_DIFF__DELTA, [COMPUTE_DELTA_ABS] = PERF_HPP_DIFF__DELTA_ABS, [COMPUTE_RATIO] = PERF_HPP_DIFF__RATIO, [COMPUTE_WEIGHTED_DIFF] = PERF_HPP_DIFF__WEIGHTED_DIFF, }; #define MAX_COL_WIDTH 70 static struct header_column { const char *name; int width; } columns[PERF_HPP_DIFF__MAX_INDEX] = { [PERF_HPP_DIFF__BASELINE] = { .name = "Baseline", }, [PERF_HPP_DIFF__PERIOD] = { .name = "Period", .width = 14, }, [PERF_HPP_DIFF__PERIOD_BASELINE] = { .name = "Base period", .width = 14, }, [PERF_HPP_DIFF__DELTA] = { .name = "Delta", .width = 7, }, [PERF_HPP_DIFF__DELTA_ABS] = { .name = "Delta Abs", .width = 7, }, [PERF_HPP_DIFF__RATIO] = { .name = "Ratio", .width = 14, }, [PERF_HPP_DIFF__WEIGHTED_DIFF] = { .name = "Weighted diff", .width = 14, }, [PERF_HPP_DIFF__FORMULA] = { .name = "Formula", .width = MAX_COL_WIDTH, } }; static int setup_compute_opt_wdiff(char *opt) { char *w1_str = opt; char *w2_str; int ret = -EINVAL; if (!opt) goto out; w2_str = strchr(opt, ','); if (!w2_str) goto out; *w2_str++ = 0x0; if (!*w2_str) goto out; compute_wdiff_w1 = strtol(w1_str, NULL, 10); compute_wdiff_w2 = strtol(w2_str, NULL, 10); if (!compute_wdiff_w1 || !compute_wdiff_w2) goto out; pr_debug("compute wdiff w1(%" PRId64 ") w2(%" PRId64 ")\n", compute_wdiff_w1, compute_wdiff_w2); ret = 0; out: if (ret) pr_err("Failed: wrong weight data, use 'wdiff:w1,w2'\n"); return ret; } static int setup_compute_opt(char *opt) { if (compute == COMPUTE_WEIGHTED_DIFF) return setup_compute_opt_wdiff(opt); if (opt) { pr_err("Failed: extra option specified '%s'", opt); return -EINVAL; } return 0; } static int setup_compute(const struct option *opt, const char *str, int unset __maybe_unused) { int *cp = (int *) opt->value; char *cstr = (char *) str; char buf[50]; unsigned i; char *option; if (!str) { *cp = COMPUTE_DELTA; return 0; } option = strchr(str, ':'); if (option) { unsigned len = option++ - str; /* * The str data are not writeable, so we need * to use another buffer. */ /* No option value is longer. */ if (len >= sizeof(buf)) return -EINVAL; strncpy(buf, str, len); buf[len] = 0x0; cstr = buf; } for (i = 0; i < COMPUTE_MAX; i++) if (!strcmp(cstr, compute_names[i])) { *cp = i; return setup_compute_opt(option); } pr_err("Failed: '%s' is not computation method " "(use 'delta','ratio' or 'wdiff')\n", str); return -EINVAL; } static double period_percent(struct hist_entry *he, u64 period) { u64 total = hists__total_period(he->hists); return (period * 100.0) / total; } static double compute_delta(struct hist_entry *he, struct hist_entry *pair) { double old_percent = period_percent(he, he->stat.period); double new_percent = period_percent(pair, pair->stat.period); pair->diff.period_ratio_delta = new_percent - old_percent; pair->diff.computed = true; return pair->diff.period_ratio_delta; } static double compute_ratio(struct hist_entry *he, struct hist_entry *pair) { double old_period = he->stat.period ?: 1; double new_period = pair->stat.period; pair->diff.computed = true; pair->diff.period_ratio = new_period / old_period; return pair->diff.period_ratio; } static s64 compute_wdiff(struct hist_entry *he, struct hist_entry *pair) { u64 old_period = he->stat.period; u64 new_period = pair->stat.period; pair->diff.computed = true; pair->diff.wdiff = new_period * compute_wdiff_w2 - old_period * compute_wdiff_w1; return pair->diff.wdiff; } static int formula_delta(struct hist_entry *he, struct hist_entry *pair, char *buf, size_t size) { u64 he_total = he->hists->stats.total_period; u64 pair_total = pair->hists->stats.total_period; if (symbol_conf.filter_relative) { he_total = he->hists->stats.total_non_filtered_period; pair_total = pair->hists->stats.total_non_filtered_period; } return scnprintf(buf, size, "(%" PRIu64 " * 100 / %" PRIu64 ") - " "(%" PRIu64 " * 100 / %" PRIu64 ")", pair->stat.period, pair_total, he->stat.period, he_total); } static int formula_ratio(struct hist_entry *he, struct hist_entry *pair, char *buf, size_t size) { double old_period = he->stat.period; double new_period = pair->stat.period; return scnprintf(buf, size, "%.0F / %.0F", new_period, old_period); } static int formula_wdiff(struct hist_entry *he, struct hist_entry *pair, char *buf, size_t size) { u64 old_period = he->stat.period; u64 new_period = pair->stat.period; return scnprintf(buf, size, "(%" PRIu64 " * " "%" PRId64 ") - (%" PRIu64 " * " "%" PRId64 ")", new_period, compute_wdiff_w2, old_period, compute_wdiff_w1); } static int formula_fprintf(struct hist_entry *he, struct hist_entry *pair, char *buf, size_t size) { switch (compute) { case COMPUTE_DELTA: case COMPUTE_DELTA_ABS: return formula_delta(he, pair, buf, size); case COMPUTE_RATIO: return formula_ratio(he, pair, buf, size); case COMPUTE_WEIGHTED_DIFF: return formula_wdiff(he, pair, buf, size); default: BUG_ON(1); } return -1; } static int diff__process_sample_event(struct perf_tool *tool, union perf_event *event, struct perf_sample *sample, struct perf_evsel *evsel, struct machine *machine) { struct perf_diff *pdiff = container_of(tool, struct perf_diff, tool); struct addr_location al; struct hists *hists = evsel__hists(evsel); int ret = -1; if (perf_time__ranges_skip_sample(pdiff->ptime_range, pdiff->range_num, sample->time)) { return 0; } if (machine__resolve(machine, &al, sample) < 0) { pr_warning("problem processing %d event, skipping it.\n", event->header.type); return -1; } if (cpu_list && !test_bit(sample->cpu, cpu_bitmap)) { ret = 0; goto out_put; } if (!hists__add_entry(hists, &al, NULL, NULL, NULL, sample, true)) { pr_warning("problem incrementing symbol period, skipping event\n"); goto out_put; } /* * The total_period is updated here before going to the output * tree since normally only the baseline hists will call * hists__output_resort() and precompute needs the total * period in order to sort entries by percentage delta. */ hists->stats.total_period += sample->period; if (!al.filtered) hists->stats.total_non_filtered_period += sample->period; ret = 0; out_put: addr_location__put(&al); return ret; } static struct perf_diff pdiff = { .tool = { .sample = diff__process_sample_event, .mmap = perf_event__process_mmap, .mmap2 = perf_event__process_mmap2, .comm = perf_event__process_comm, .exit = perf_event__process_exit, .fork = perf_event__process_fork, .lost = perf_event__process_lost, .namespaces = perf_event__process_namespaces, .ordered_events = true, .ordering_requires_timestamps = true, }, }; static struct perf_evsel *evsel_match(struct perf_evsel *evsel, struct perf_evlist *evlist) { struct perf_evsel *e; evlist__for_each_entry(evlist, e) { if (perf_evsel__match2(evsel, e)) return e; } return NULL; } static void perf_evlist__collapse_resort(struct perf_evlist *evlist) { struct perf_evsel *evsel; evlist__for_each_entry(evlist, evsel) { struct hists *hists = evsel__hists(evsel); hists__collapse_resort(hists, NULL); } } static struct data__file *fmt_to_data_file(struct perf_hpp_fmt *fmt) { struct diff_hpp_fmt *dfmt = container_of(fmt, struct diff_hpp_fmt, fmt); void *ptr = dfmt - dfmt->idx; struct data__file *d = container_of(ptr, struct data__file, fmt); return d; } static struct hist_entry* get_pair_data(struct hist_entry *he, struct data__file *d) { if (hist_entry__has_pairs(he)) { struct hist_entry *pair; list_for_each_entry(pair, &he->pairs.head, pairs.node) if (pair->hists == d->hists) return pair; } return NULL; } static struct hist_entry* get_pair_fmt(struct hist_entry *he, struct diff_hpp_fmt *dfmt) { struct data__file *d = fmt_to_data_file(&dfmt->fmt); return get_pair_data(he, d); } static void hists__baseline_only(struct hists *hists) { struct rb_root_cached *root; struct rb_node *next; if (hists__has(hists, need_collapse)) root = &hists->entries_collapsed; else root = hists->entries_in; next = rb_first_cached(root); while (next != NULL) { struct hist_entry *he = rb_entry(next, struct hist_entry, rb_node_in); next = rb_next(&he->rb_node_in); if (!hist_entry__next_pair(he)) { rb_erase_cached(&he->rb_node_in, root); hist_entry__delete(he); } } } static void hists__precompute(struct hists *hists) { struct rb_root_cached *root; struct rb_node *next; if (hists__has(hists, need_collapse)) root = &hists->entries_collapsed; else root = hists->entries_in; next = rb_first_cached(root); while (next != NULL) { struct hist_entry *he, *pair; struct data__file *d; int i; he = rb_entry(next, struct hist_entry, rb_node_in); next = rb_next(&he->rb_node_in); data__for_each_file_new(i, d) { pair = get_pair_data(he, d); if (!pair) continue;