Commit 7d9e55fe authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'perf-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip

Pull perf fixes from Thomas Gleixner:
 "The perf update contains the following bits:

  x86:
   - Prevent setting freeze_on_smi on PerfMon V1 CPUs to avoid #GP

  perf stat:
   - Keep the '/' event modifier separator in fallback, for example when
     fallbacking from 'cpu/cpu-cycles/' to user level only, where it
     should become 'cpu/cpu-cycles/u' and not 'cpu/cpu-cycles/:u' (Jiri
     Olsa)

   - Fix PMU events parsing rule, improving error reporting for invalid
     events (Jiri Olsa)

   - Disable write_backward and other event attributes for !group events
     in a group, fixing, for instance this group: '{cycles,msr/aperf/}:S'
     that has leader sampling (:S) and where just the 'cycles', the
     leader event, should have the write_backward attribute set, in this
     case it all fails because the PMU where 'msr/aperf/' lives doesn't
     accepts write_backward style sampling (Jiri Olsa)

   - Only fall back group read for leader (Kan Liang)

   - Fix core PMU alias list for x86 platform (Kan Liang)

   - Print out hint for mixed PMU group error (Kan Liang)

   - Fix duplicate PMU name for interval print (Kan Liang)

  Core:
   - Set main kernel end address properly when reading kernel and module
     maps (Namhyung Kim)

  perf mem:
   - Fix incorrect entries and add missing man options (Sangwon Hong)

  s/390:
   - Remove s390 specific strcmp_cpuid_cmp function (Thomas Richter)

   - Adapt 'perf test' case record+probe_libc_inet_pton.sh for s390

   - Fix s390 undefined record__auxtrace_init() return value in 'perf
     record' (Thomas Richter)"

* 'perf-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip:
  perf/x86/intel: Don't enable freeze-on-smi for PerfMon V1
  perf stat: Fix duplicate PMU name for interval print
  perf evsel: Only fall back group read for leader
  perf stat: Print out hint for mixed PMU group error
  perf pmu: Fix core PMU alias list for X86 platform
  perf record: Fix s390 undefined record__auxtrace_init() return value
  perf mem: Document incorrect and missing options
  perf evsel: Disable write_backward for leader sampling group events
  perf pmu: Fix pmu events parsing rule
  perf stat: Keep the / modifier separator in fallback
  perf test: Adapt test case record+probe_libc_inet_pton.sh for s390
  perf list: Remove s390 specific strcmp_cpuid_cmp function
  perf machine: Set main kernel end address properly
parents cdface52 d4652f61
...@@ -3339,7 +3339,8 @@ static void intel_pmu_cpu_starting(int cpu) ...@@ -3339,7 +3339,8 @@ static void intel_pmu_cpu_starting(int cpu)
cpuc->lbr_sel = NULL; cpuc->lbr_sel = NULL;
flip_smm_bit(&x86_pmu.attr_freeze_on_smi); if (x86_pmu.version > 1)
flip_smm_bit(&x86_pmu.attr_freeze_on_smi);
if (!cpuc->shared_regs) if (!cpuc->shared_regs)
return; return;
...@@ -3502,6 +3503,8 @@ static __initconst const struct x86_pmu core_pmu = { ...@@ -3502,6 +3503,8 @@ static __initconst const struct x86_pmu core_pmu = {
.cpu_dying = intel_pmu_cpu_dying, .cpu_dying = intel_pmu_cpu_dying,
}; };
static struct attribute *intel_pmu_attrs[];
static __initconst const struct x86_pmu intel_pmu = { static __initconst const struct x86_pmu intel_pmu = {
.name = "Intel", .name = "Intel",
.handle_irq = intel_pmu_handle_irq, .handle_irq = intel_pmu_handle_irq,
...@@ -3533,6 +3536,8 @@ static __initconst const struct x86_pmu intel_pmu = { ...@@ -3533,6 +3536,8 @@ static __initconst const struct x86_pmu intel_pmu = {
.format_attrs = intel_arch3_formats_attr, .format_attrs = intel_arch3_formats_attr,
.events_sysfs_show = intel_event_sysfs_show, .events_sysfs_show = intel_event_sysfs_show,
.attrs = intel_pmu_attrs,
.cpu_prepare = intel_pmu_cpu_prepare, .cpu_prepare = intel_pmu_cpu_prepare,
.cpu_starting = intel_pmu_cpu_starting, .cpu_starting = intel_pmu_cpu_starting,
.cpu_dying = intel_pmu_cpu_dying, .cpu_dying = intel_pmu_cpu_dying,
...@@ -3911,8 +3916,6 @@ __init int intel_pmu_init(void) ...@@ -3911,8 +3916,6 @@ __init int intel_pmu_init(void)
x86_pmu.max_pebs_events = min_t(unsigned, MAX_PEBS_EVENTS, x86_pmu.num_counters); x86_pmu.max_pebs_events = min_t(unsigned, MAX_PEBS_EVENTS, x86_pmu.num_counters);
x86_pmu.attrs = intel_pmu_attrs;
/* /*
* Quirk: v2 perfmon does not report fixed-purpose events, so * Quirk: v2 perfmon does not report fixed-purpose events, so
* assume at least 3 events, when not running in a hypervisor: * assume at least 3 events, when not running in a hypervisor:
......
...@@ -28,29 +28,46 @@ OPTIONS ...@@ -28,29 +28,46 @@ OPTIONS
<command>...:: <command>...::
Any command you can specify in a shell. Any command you can specify in a shell.
-i::
--input=<file>::
Input file name.
-f:: -f::
--force:: --force::
Don't do ownership validation Don't do ownership validation
-t:: -t::
--type=:: --type=<type>::
Select the memory operation type: load or store (default: load,store) Select the memory operation type: load or store (default: load,store)
-D:: -D::
--dump-raw-samples=:: --dump-raw-samples::
Dump the raw decoded samples on the screen in a format that is easy to parse with Dump the raw decoded samples on the screen in a format that is easy to parse with
one sample per line. one sample per line.
-x:: -x::
--field-separator:: --field-separator=<separator>::
Specify the field separator used when dump raw samples (-D option). By default, Specify the field separator used when dump raw samples (-D option). By default,
The separator is the space character. The separator is the space character.
-C:: -C::
--cpu-list:: --cpu=<cpu>::
Restrict dump of raw samples to those provided via this option. Note that the same Monitor only on the list of CPUs provided. Multiple CPUs can be provided as a
option can be passed in record mode. It will be interpreted the same way as perf comma-separated list with no space: 0,1. Ranges of CPUs are specified with -: 0-2. Default
record. is to monitor all CPUS.
-U::
--hide-unresolved::
Only display entries resolved to a symbol.
-p::
--phys-data::
Record/Report sample physical addresses
RECORD OPTIONS
--------------
-e::
--event <event>::
Event selector. Use 'perf mem record -e list' to list available events.
-K:: -K::
--all-kernel:: --all-kernel::
...@@ -60,12 +77,12 @@ OPTIONS ...@@ -60,12 +77,12 @@ OPTIONS
--all-user:: --all-user::
Configure all used events to run in user space. Configure all used events to run in user space.
--ldload:: -v::
Specify desired latency for loads event. --verbose::
Be more verbose (show counter open errors, etc)
-p:: --ldlat <n>::
--phys-data:: Specify desired latency for loads event.
Record/Report sample physical addresses
In addition, for report all perf report options are valid, and for record In addition, for report all perf report options are valid, and for record
all perf record options. all perf record options.
......
...@@ -87,6 +87,7 @@ struct auxtrace_record *auxtrace_record__init(struct perf_evlist *evlist, ...@@ -87,6 +87,7 @@ struct auxtrace_record *auxtrace_record__init(struct perf_evlist *evlist,
struct perf_evsel *pos; struct perf_evsel *pos;
int diagnose = 0; int diagnose = 0;
*err = 0;
if (evlist->nr_entries == 0) if (evlist->nr_entries == 0)
return NULL; return NULL;
......
...@@ -146,21 +146,3 @@ char *get_cpuid_str(struct perf_pmu *pmu __maybe_unused) ...@@ -146,21 +146,3 @@ char *get_cpuid_str(struct perf_pmu *pmu __maybe_unused)
zfree(&buf); zfree(&buf);
return buf; return buf;
} }
/*
* Compare the cpuid string returned by get_cpuid() function
* with the name generated by the jevents file read from
* pmu-events/arch/s390/mapfile.csv.
*
* Parameter mapcpuid is the cpuid as stored in the
* pmu-events/arch/s390/mapfile.csv. This is just the type number.
* Parameter cpuid is the cpuid returned by function get_cpuid().
*/
int strcmp_cpuid_str(const char *mapcpuid, const char *cpuid)
{
char *cp = strchr(cpuid, ',');
if (cp == NULL)
return -1;
return strncmp(cp + 1, mapcpuid, strlen(mapcpuid));
}
...@@ -172,6 +172,7 @@ static bool interval_count; ...@@ -172,6 +172,7 @@ static bool interval_count;
static const char *output_name; static const char *output_name;
static int output_fd; static int output_fd;
static int print_free_counters_hint; static int print_free_counters_hint;
static int print_mixed_hw_group_error;
struct perf_stat { struct perf_stat {
bool record; bool record;
...@@ -1126,6 +1127,30 @@ static void abs_printout(int id, int nr, struct perf_evsel *evsel, double avg) ...@@ -1126,6 +1127,30 @@ static void abs_printout(int id, int nr, struct perf_evsel *evsel, double avg)
fprintf(output, "%s%s", csv_sep, evsel->cgrp->name); fprintf(output, "%s%s", csv_sep, evsel->cgrp->name);
} }
static bool is_mixed_hw_group(struct perf_evsel *counter)
{
struct perf_evlist *evlist = counter->evlist;
u32 pmu_type = counter->attr.type;
struct perf_evsel *pos;
if (counter->nr_members < 2)
return false;
evlist__for_each_entry(evlist, pos) {
/* software events can be part of any hardware group */
if (pos->attr.type == PERF_TYPE_SOFTWARE)
continue;
if (pmu_type == PERF_TYPE_SOFTWARE) {
pmu_type = pos->attr.type;
continue;
}
if (pmu_type != pos->attr.type)
return true;
}
return false;
}
static void printout(int id, int nr, struct perf_evsel *counter, double uval, static void printout(int id, int nr, struct perf_evsel *counter, double uval,
char *prefix, u64 run, u64 ena, double noise, char *prefix, u64 run, u64 ena, double noise,
struct runtime_stat *st) struct runtime_stat *st)
...@@ -1178,8 +1203,11 @@ static void printout(int id, int nr, struct perf_evsel *counter, double uval, ...@@ -1178,8 +1203,11 @@ static void printout(int id, int nr, struct perf_evsel *counter, double uval,
counter->supported ? CNTR_NOT_COUNTED : CNTR_NOT_SUPPORTED, counter->supported ? CNTR_NOT_COUNTED : CNTR_NOT_SUPPORTED,
csv_sep); csv_sep);
if (counter->supported) if (counter->supported) {
print_free_counters_hint = 1; print_free_counters_hint = 1;
if (is_mixed_hw_group(counter))
print_mixed_hw_group_error = 1;
}
fprintf(stat_config.output, "%-*s%s", fprintf(stat_config.output, "%-*s%s",
csv_output ? 0 : unit_width, csv_output ? 0 : unit_width,
...@@ -1256,7 +1284,8 @@ static void uniquify_event_name(struct perf_evsel *counter) ...@@ -1256,7 +1284,8 @@ static void uniquify_event_name(struct perf_evsel *counter)
char *new_name; char *new_name;
char *config; char *config;
if (!counter->pmu_name || !strncmp(counter->name, counter->pmu_name, if (counter->uniquified_name ||
!counter->pmu_name || !strncmp(counter->name, counter->pmu_name,
strlen(counter->pmu_name))) strlen(counter->pmu_name)))
return; return;
...@@ -1274,6 +1303,8 @@ static void uniquify_event_name(struct perf_evsel *counter) ...@@ -1274,6 +1303,8 @@ static void uniquify_event_name(struct perf_evsel *counter)
counter->name = new_name; counter->name = new_name;
} }
} }
counter->uniquified_name = true;
} }
static void collect_all_aliases(struct perf_evsel *counter, static void collect_all_aliases(struct perf_evsel *counter,
...@@ -1757,6 +1788,11 @@ static void print_footer(void) ...@@ -1757,6 +1788,11 @@ static void print_footer(void)
" echo 0 > /proc/sys/kernel/nmi_watchdog\n" " echo 0 > /proc/sys/kernel/nmi_watchdog\n"
" perf stat ...\n" " perf stat ...\n"
" echo 1 > /proc/sys/kernel/nmi_watchdog\n"); " echo 1 > /proc/sys/kernel/nmi_watchdog\n");
if (print_mixed_hw_group_error)
fprintf(output,
"The events in group usually have to be from "
"the same PMU. Try reorganizing the group.\n");
} }
static void print_counters(struct timespec *ts, int argc, const char **argv) static void print_counters(struct timespec *ts, int argc, const char **argv)
......
Family-model,Version,Filename,EventType Family-model,Version,Filename,EventType
209[78],1,cf_z10,core ^IBM.209[78].*[13]\.[1-5].[[:xdigit:]]+$,1,cf_z10,core
281[78],1,cf_z196,core ^IBM.281[78].*[13]\.[1-5].[[:xdigit:]]+$,1,cf_z196,core
282[78],1,cf_zec12,core ^IBM.282[78].*[13]\.[1-5].[[:xdigit:]]+$,1,cf_zec12,core
296[45],1,cf_z13,core ^IBM.296[45].*[13]\.[1-5].[[:xdigit:]]+$,1,cf_z13,core
3906,3,cf_z14,core ^IBM.390[67].*[13]\.[1-5].[[:xdigit:]]+$,3,cf_z14,core
...@@ -35,3 +35,6 @@ inherit=0 ...@@ -35,3 +35,6 @@ inherit=0
# sampling disabled # sampling disabled
sample_freq=0 sample_freq=0
sample_period=0 sample_period=0
freq=0
write_backward=0
sample_id_all=0
...@@ -19,12 +19,10 @@ trace_libc_inet_pton_backtrace() { ...@@ -19,12 +19,10 @@ trace_libc_inet_pton_backtrace() {
expected[1]=".*inet_pton[[:space:]]\($libc\)$" expected[1]=".*inet_pton[[:space:]]\($libc\)$"
case "$(uname -m)" in case "$(uname -m)" in
s390x) s390x)
eventattr='call-graph=dwarf' eventattr='call-graph=dwarf,max-stack=4'
expected[2]="gaih_inet.*[[:space:]]\($libc|inlined\)$" expected[2]="gaih_inet.*[[:space:]]\($libc|inlined\)$"
expected[3]="__GI_getaddrinfo[[:space:]]\($libc|inlined\)$" expected[3]="(__GI_)?getaddrinfo[[:space:]]\($libc|inlined\)$"
expected[4]="main[[:space:]]\(.*/bin/ping.*\)$" expected[4]="main[[:space:]]\(.*/bin/ping.*\)$"
expected[5]="__libc_start_main[[:space:]]\($libc\)$"
expected[6]="_start[[:space:]]\(.*/bin/ping.*\)$"
;; ;;
*) *)
eventattr='max-stack=3' eventattr='max-stack=3'
......
...@@ -930,8 +930,11 @@ void perf_evsel__config(struct perf_evsel *evsel, struct record_opts *opts, ...@@ -930,8 +930,11 @@ void perf_evsel__config(struct perf_evsel *evsel, struct record_opts *opts,
* than leader in case leader 'leads' the sampling. * than leader in case leader 'leads' the sampling.
*/ */
if ((leader != evsel) && leader->sample_read) { if ((leader != evsel) && leader->sample_read) {
attr->sample_freq = 0; attr->freq = 0;
attr->sample_period = 0; attr->sample_freq = 0;
attr->sample_period = 0;
attr->write_backward = 0;
attr->sample_id_all = 0;
} }
if (opts->no_samples) if (opts->no_samples)
...@@ -1922,7 +1925,8 @@ int perf_evsel__open(struct perf_evsel *evsel, struct cpu_map *cpus, ...@@ -1922,7 +1925,8 @@ int perf_evsel__open(struct perf_evsel *evsel, struct cpu_map *cpus,
goto fallback_missing_features; goto fallback_missing_features;
} else if (!perf_missing_features.group_read && } else if (!perf_missing_features.group_read &&
evsel->attr.inherit && evsel->attr.inherit &&
(evsel->attr.read_format & PERF_FORMAT_GROUP)) { (evsel->attr.read_format & PERF_FORMAT_GROUP) &&
perf_evsel__is_group_leader(evsel)) {
perf_missing_features.group_read = true; perf_missing_features.group_read = true;
pr_debug2("switching off group read\n"); pr_debug2("switching off group read\n");
goto fallback_missing_features; goto fallback_missing_features;
...@@ -2754,8 +2758,14 @@ bool perf_evsel__fallback(struct perf_evsel *evsel, int err, ...@@ -2754,8 +2758,14 @@ bool perf_evsel__fallback(struct perf_evsel *evsel, int err,
(paranoid = perf_event_paranoid()) > 1) { (paranoid = perf_event_paranoid()) > 1) {
const char *name = perf_evsel__name(evsel); const char *name = perf_evsel__name(evsel);
char *new_name; char *new_name;
const char *sep = ":";
if (asprintf(&new_name, "%s%su", name, strchr(name, ':') ? "" : ":") < 0) /* Is there already the separator in the name. */
if (strchr(name, '/') ||
strchr(name, ':'))
sep = "";
if (asprintf(&new_name, "%s%su", name, sep) < 0)
return false; return false;
if (evsel->name) if (evsel->name)
......
...@@ -115,6 +115,7 @@ struct perf_evsel { ...@@ -115,6 +115,7 @@ struct perf_evsel {
unsigned int sample_size; unsigned int sample_size;
int id_pos; int id_pos;
int is_pos; int is_pos;
bool uniquified_name;
bool snapshot; bool snapshot;
bool supported; bool supported;
bool needs_swap; bool needs_swap;
......
...@@ -1019,13 +1019,6 @@ int machine__load_vmlinux_path(struct machine *machine, enum map_type type) ...@@ -1019,13 +1019,6 @@ int machine__load_vmlinux_path(struct machine *machine, enum map_type type)
return ret; return ret;
} }
static void map_groups__fixup_end(struct map_groups *mg)
{
int i;
for (i = 0; i < MAP__NR_TYPES; ++i)
__map_groups__fixup_end(mg, i);
}
static char *get_kernel_version(const char *root_dir) static char *get_kernel_version(const char *root_dir)
{ {
char version[PATH_MAX]; char version[PATH_MAX];
...@@ -1233,6 +1226,7 @@ int machine__create_kernel_maps(struct machine *machine) ...@@ -1233,6 +1226,7 @@ int machine__create_kernel_maps(struct machine *machine)
{ {
struct dso *kernel = machine__get_kernel(machine); struct dso *kernel = machine__get_kernel(machine);
const char *name = NULL; const char *name = NULL;
struct map *map;
u64 addr = 0; u64 addr = 0;
int ret; int ret;
...@@ -1259,13 +1253,25 @@ int machine__create_kernel_maps(struct machine *machine) ...@@ -1259,13 +1253,25 @@ int machine__create_kernel_maps(struct machine *machine)
machine__destroy_kernel_maps(machine); machine__destroy_kernel_maps(machine);
return -1; return -1;
} }
machine__set_kernel_mmap(machine, addr, 0);
/* we have a real start address now, so re-order the kmaps */
map = machine__kernel_map(machine);
map__get(map);
map_groups__remove(&machine->kmaps, map);
/* assume it's the last in the kmaps */
machine__set_kernel_mmap(machine, addr, ~0ULL);
map_groups__insert(&machine->kmaps, map);
map__put(map);
} }
/* /* update end address of the kernel map using adjacent module address */
* Now that we have all the maps created, just set the ->end of them: map = map__next(machine__kernel_map(machine));
*/ if (map)
map_groups__fixup_end(&machine->kmaps); machine__set_kernel_mmap(machine, addr, map->start);
return 0; return 0;
} }
......
...@@ -224,15 +224,15 @@ event_def: event_pmu | ...@@ -224,15 +224,15 @@ event_def: event_pmu |
event_bpf_file event_bpf_file
event_pmu: event_pmu:
PE_NAME opt_event_config PE_NAME '/' event_config '/'
{ {
struct list_head *list, *orig_terms, *terms; struct list_head *list, *orig_terms, *terms;
if (parse_events_copy_term_list($2, &orig_terms)) if (parse_events_copy_term_list($3, &orig_terms))
YYABORT; YYABORT;
ALLOC_LIST(list); ALLOC_LIST(list);
if (parse_events_add_pmu(_parse_state, list, $1, $2, false)) { if (parse_events_add_pmu(_parse_state, list, $1, $3, false)) {
struct perf_pmu *pmu = NULL; struct perf_pmu *pmu = NULL;
int ok = 0; int ok = 0;
char *pattern; char *pattern;
...@@ -262,7 +262,7 @@ PE_NAME opt_event_config ...@@ -262,7 +262,7 @@ PE_NAME opt_event_config
if (!ok) if (!ok)
YYABORT; YYABORT;
} }
parse_events_terms__delete($2); parse_events_terms__delete($3);
parse_events_terms__delete(orig_terms); parse_events_terms__delete(orig_terms);
$$ = list; $$ = list;
} }
......
...@@ -539,9 +539,10 @@ static bool pmu_is_uncore(const char *name) ...@@ -539,9 +539,10 @@ static bool pmu_is_uncore(const char *name)
/* /*
* PMU CORE devices have different name other than cpu in sysfs on some * PMU CORE devices have different name other than cpu in sysfs on some
* platforms. looking for possible sysfs files to identify as core device. * platforms.
* Looking for possible sysfs files to identify the arm core device.
*/ */
static int is_pmu_core(const char *name) static int is_arm_pmu_core(const char *name)
{ {
struct stat st; struct stat st;
char path[PATH_MAX]; char path[PATH_MAX];
...@@ -550,12 +551,6 @@ static int is_pmu_core(const char *name) ...@@ -550,12 +551,6 @@ static int is_pmu_core(const char *name)
if (!sysfs) if (!sysfs)
return 0; return 0;
/* Look for cpu sysfs (x86 and others) */
scnprintf(path, PATH_MAX, "%s/bus/event_source/devices/cpu", sysfs);
if ((stat(path, &st) == 0) &&
(strncmp(name, "cpu", strlen("cpu")) == 0))
return 1;
/* Look for cpu sysfs (specific to arm) */ /* Look for cpu sysfs (specific to arm) */
scnprintf(path, PATH_MAX, "%s/bus/event_source/devices/%s/cpus", scnprintf(path, PATH_MAX, "%s/bus/event_source/devices/%s/cpus",
sysfs, name); sysfs, name);
...@@ -586,7 +581,7 @@ char * __weak get_cpuid_str(struct perf_pmu *pmu __maybe_unused) ...@@ -586,7 +581,7 @@ char * __weak get_cpuid_str(struct perf_pmu *pmu __maybe_unused)
* cpuid string generated on this platform. * cpuid string generated on this platform.
* Otherwise return non-zero. * Otherwise return non-zero.
*/ */
int __weak strcmp_cpuid_str(const char *mapcpuid, const char *cpuid) int strcmp_cpuid_str(const char *mapcpuid, const char *cpuid)
{ {
regex_t re; regex_t re;
regmatch_t pmatch[1]; regmatch_t pmatch[1];
...@@ -668,6 +663,7 @@ static void pmu_add_cpu_aliases(struct list_head *head, struct perf_pmu *pmu) ...@@ -668,6 +663,7 @@ static void pmu_add_cpu_aliases(struct list_head *head, struct perf_pmu *pmu)
struct pmu_events_map *map; struct pmu_events_map *map;
struct pmu_event *pe; struct pmu_event *pe;
const char *name = pmu->name; const char *name = pmu->name;
const char *pname;
map = perf_pmu__find_map(pmu); map = perf_pmu__find_map(pmu);
if (!map) if (!map)
...@@ -686,11 +682,9 @@ static void pmu_add_cpu_aliases(struct list_head *head, struct perf_pmu *pmu) ...@@ -686,11 +682,9 @@ static void pmu_add_cpu_aliases(struct list_head *head, struct perf_pmu *pmu)
break; break;
} }
if (!is_pmu_core(name)) { if (!is_arm_pmu_core(name)) {
/* check for uncore devices */ pname = pe->pmu ? pe->pmu : "cpu";
if (pe->pmu == NULL) if (strncmp(pname, name, strlen(pname)))
continue;
if (strncmp(pe->pmu, name, strlen(pe->pmu)))
continue; continue;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment