Commit 267dd0a0 authored by Ingo Molnar's avatar Ingo Molnar

Merge tag 'perf-core-for-mingo-4.12-20170320' of...

Merge tag 'perf-core-for-mingo-4.12-20170320' of git://git.kernel.org/pub/scm/linux/kernel/git/acme/linux into perf/core

Pull perf/core improvements and fixes from Arnaldo Carvalho de Melo:

Fixes:

- Fix concat_probe_trace_events() in 'perf probe', it should dereference a
  pointer, not test its value (Ravi Bangoria)

User visible changes:

- Handle partial AUX records, checking if 'kvm_intel.ko' is loaded and
  if its 'vmm_exclusive' parameter is set to 0, suggesting tweaking
  it to reduce gaps (Alexander Shishkin)

Infrastructure changes:

- Sync the kvm.h, cpufeatures.h and perf_event.h tools/ headers copies
  with the kernel (Arnaldo Carvalho de Melo, Alexander Shishkin)

- 'perf lock' subcommands should include common options, using
  OPT_PARENT() (Changbin Du)

- Ditto for 'perf timechart' (Arnaldo Carvalho de Melo)

Documentation changes:

  Correct 'perf stat --no-aggr' description (Ravi Bangoria)
Signed-off-by: default avatarArnaldo Carvalho de Melo <acme@redhat.com>
Signed-off-by: default avatarIngo Molnar <mingo@kernel.org>
parents 61f63e38 affa6c16
...@@ -181,10 +181,23 @@ struct kvm_arch_memory_slot { ...@@ -181,10 +181,23 @@ struct kvm_arch_memory_slot {
#define KVM_DEV_ARM_VGIC_GRP_CPU_REGS 2 #define KVM_DEV_ARM_VGIC_GRP_CPU_REGS 2
#define KVM_DEV_ARM_VGIC_CPUID_SHIFT 32 #define KVM_DEV_ARM_VGIC_CPUID_SHIFT 32
#define KVM_DEV_ARM_VGIC_CPUID_MASK (0xffULL << KVM_DEV_ARM_VGIC_CPUID_SHIFT) #define KVM_DEV_ARM_VGIC_CPUID_MASK (0xffULL << KVM_DEV_ARM_VGIC_CPUID_SHIFT)
#define KVM_DEV_ARM_VGIC_V3_MPIDR_SHIFT 32
#define KVM_DEV_ARM_VGIC_V3_MPIDR_MASK \
(0xffffffffULL << KVM_DEV_ARM_VGIC_V3_MPIDR_SHIFT)
#define KVM_DEV_ARM_VGIC_OFFSET_SHIFT 0 #define KVM_DEV_ARM_VGIC_OFFSET_SHIFT 0
#define KVM_DEV_ARM_VGIC_OFFSET_MASK (0xffffffffULL << KVM_DEV_ARM_VGIC_OFFSET_SHIFT) #define KVM_DEV_ARM_VGIC_OFFSET_MASK (0xffffffffULL << KVM_DEV_ARM_VGIC_OFFSET_SHIFT)
#define KVM_DEV_ARM_VGIC_SYSREG_INSTR_MASK (0xffff)
#define KVM_DEV_ARM_VGIC_GRP_NR_IRQS 3 #define KVM_DEV_ARM_VGIC_GRP_NR_IRQS 3
#define KVM_DEV_ARM_VGIC_GRP_CTRL 4 #define KVM_DEV_ARM_VGIC_GRP_CTRL 4
#define KVM_DEV_ARM_VGIC_GRP_REDIST_REGS 5
#define KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS 6
#define KVM_DEV_ARM_VGIC_GRP_LEVEL_INFO 7
#define KVM_DEV_ARM_VGIC_LINE_LEVEL_INFO_SHIFT 10
#define KVM_DEV_ARM_VGIC_LINE_LEVEL_INFO_MASK \
(0x3fffffULL << KVM_DEV_ARM_VGIC_LINE_LEVEL_INFO_SHIFT)
#define KVM_DEV_ARM_VGIC_LINE_LEVEL_INTID_MASK 0x3ff
#define VGIC_LEVEL_INFO_LINE_LEVEL 0
#define KVM_DEV_ARM_VGIC_CTRL_INIT 0 #define KVM_DEV_ARM_VGIC_CTRL_INIT 0
/* KVM_IRQ_LINE irq field index values */ /* KVM_IRQ_LINE irq field index values */
......
...@@ -201,10 +201,23 @@ struct kvm_arch_memory_slot { ...@@ -201,10 +201,23 @@ struct kvm_arch_memory_slot {
#define KVM_DEV_ARM_VGIC_GRP_CPU_REGS 2 #define KVM_DEV_ARM_VGIC_GRP_CPU_REGS 2
#define KVM_DEV_ARM_VGIC_CPUID_SHIFT 32 #define KVM_DEV_ARM_VGIC_CPUID_SHIFT 32
#define KVM_DEV_ARM_VGIC_CPUID_MASK (0xffULL << KVM_DEV_ARM_VGIC_CPUID_SHIFT) #define KVM_DEV_ARM_VGIC_CPUID_MASK (0xffULL << KVM_DEV_ARM_VGIC_CPUID_SHIFT)
#define KVM_DEV_ARM_VGIC_V3_MPIDR_SHIFT 32
#define KVM_DEV_ARM_VGIC_V3_MPIDR_MASK \
(0xffffffffULL << KVM_DEV_ARM_VGIC_V3_MPIDR_SHIFT)
#define KVM_DEV_ARM_VGIC_OFFSET_SHIFT 0 #define KVM_DEV_ARM_VGIC_OFFSET_SHIFT 0
#define KVM_DEV_ARM_VGIC_OFFSET_MASK (0xffffffffULL << KVM_DEV_ARM_VGIC_OFFSET_SHIFT) #define KVM_DEV_ARM_VGIC_OFFSET_MASK (0xffffffffULL << KVM_DEV_ARM_VGIC_OFFSET_SHIFT)
#define KVM_DEV_ARM_VGIC_SYSREG_INSTR_MASK (0xffff)
#define KVM_DEV_ARM_VGIC_GRP_NR_IRQS 3 #define KVM_DEV_ARM_VGIC_GRP_NR_IRQS 3
#define KVM_DEV_ARM_VGIC_GRP_CTRL 4 #define KVM_DEV_ARM_VGIC_GRP_CTRL 4
#define KVM_DEV_ARM_VGIC_GRP_REDIST_REGS 5
#define KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS 6
#define KVM_DEV_ARM_VGIC_GRP_LEVEL_INFO 7
#define KVM_DEV_ARM_VGIC_LINE_LEVEL_INFO_SHIFT 10
#define KVM_DEV_ARM_VGIC_LINE_LEVEL_INFO_MASK \
(0x3fffffULL << KVM_DEV_ARM_VGIC_LINE_LEVEL_INFO_SHIFT)
#define KVM_DEV_ARM_VGIC_LINE_LEVEL_INTID_MASK 0x3ff
#define VGIC_LEVEL_INFO_LINE_LEVEL 0
#define KVM_DEV_ARM_VGIC_CTRL_INIT 0 #define KVM_DEV_ARM_VGIC_CTRL_INIT 0
/* Device Control API on vcpu fd */ /* Device Control API on vcpu fd */
......
...@@ -413,6 +413,26 @@ struct kvm_get_htab_header { ...@@ -413,6 +413,26 @@ struct kvm_get_htab_header {
__u16 n_invalid; __u16 n_invalid;
}; };
/* For KVM_PPC_CONFIGURE_V3_MMU */
struct kvm_ppc_mmuv3_cfg {
__u64 flags;
__u64 process_table; /* second doubleword of partition table entry */
};
/* Flag values for KVM_PPC_CONFIGURE_V3_MMU */
#define KVM_PPC_MMUV3_RADIX 1 /* 1 = radix mode, 0 = HPT */
#define KVM_PPC_MMUV3_GTSE 2 /* global translation shootdown enb. */
/* For KVM_PPC_GET_RMMU_INFO */
struct kvm_ppc_rmmu_info {
struct kvm_ppc_radix_geom {
__u8 page_shift;
__u8 level_bits[4];
__u8 pad[3];
} geometries[8];
__u32 ap_encodings[8];
};
/* Per-vcpu XICS interrupt controller state */ /* Per-vcpu XICS interrupt controller state */
#define KVM_REG_PPC_ICP_STATE (KVM_REG_PPC | KVM_REG_SIZE_U64 | 0x8c) #define KVM_REG_PPC_ICP_STATE (KVM_REG_PPC | KVM_REG_SIZE_U64 | 0x8c)
...@@ -613,5 +633,7 @@ struct kvm_get_htab_header { ...@@ -613,5 +633,7 @@ struct kvm_get_htab_header {
#define KVM_XICS_LEVEL_SENSITIVE (1ULL << 40) #define KVM_XICS_LEVEL_SENSITIVE (1ULL << 40)
#define KVM_XICS_MASKED (1ULL << 41) #define KVM_XICS_MASKED (1ULL << 41)
#define KVM_XICS_PENDING (1ULL << 42) #define KVM_XICS_PENDING (1ULL << 42)
#define KVM_XICS_PRESENTED (1ULL << 43)
#define KVM_XICS_QUEUED (1ULL << 44)
#endif /* __LINUX_KVM_POWERPC_H */ #endif /* __LINUX_KVM_POWERPC_H */
...@@ -289,7 +289,8 @@ ...@@ -289,7 +289,8 @@
#define X86_FEATURE_PKU (16*32+ 3) /* Protection Keys for Userspace */ #define X86_FEATURE_PKU (16*32+ 3) /* Protection Keys for Userspace */
#define X86_FEATURE_OSPKE (16*32+ 4) /* OS Protection Keys Enable */ #define X86_FEATURE_OSPKE (16*32+ 4) /* OS Protection Keys Enable */
#define X86_FEATURE_AVX512_VPOPCNTDQ (16*32+14) /* POPCNT for vectors of DW/QW */ #define X86_FEATURE_AVX512_VPOPCNTDQ (16*32+14) /* POPCNT for vectors of DW/QW */
#define X86_FEATURE_RDPID (16*32+ 22) /* RDPID instruction */ #define X86_FEATURE_LA57 (16*32+16) /* 5-level page tables */
#define X86_FEATURE_RDPID (16*32+22) /* RDPID instruction */
/* AMD-defined CPU features, CPUID level 0x80000007 (ebx), word 17 */ /* AMD-defined CPU features, CPUID level 0x80000007 (ebx), word 17 */
#define X86_FEATURE_OVERFLOW_RECOV (17*32+0) /* MCA overflow recovery support */ #define X86_FEATURE_OVERFLOW_RECOV (17*32+0) /* MCA overflow recovery support */
......
...@@ -915,6 +915,7 @@ enum perf_callchain_context { ...@@ -915,6 +915,7 @@ enum perf_callchain_context {
*/ */
#define PERF_AUX_FLAG_TRUNCATED 0x01 /* record was truncated to fit */ #define PERF_AUX_FLAG_TRUNCATED 0x01 /* record was truncated to fit */
#define PERF_AUX_FLAG_OVERWRITE 0x02 /* snapshot from overwrite mode */ #define PERF_AUX_FLAG_OVERWRITE 0x02 /* snapshot from overwrite mode */
#define PERF_AUX_FLAG_PARTIAL 0x04 /* record contains gaps */
#define PERF_FLAG_FD_NO_GROUP (1UL << 0) #define PERF_FLAG_FD_NO_GROUP (1UL << 0)
#define PERF_FLAG_FD_OUTPUT (1UL << 1) #define PERF_FLAG_FD_OUTPUT (1UL << 1)
......
...@@ -439,6 +439,35 @@ int sysfs__read_str(const char *entry, char **buf, size_t *sizep) ...@@ -439,6 +439,35 @@ int sysfs__read_str(const char *entry, char **buf, size_t *sizep)
return filename__read_str(path, buf, sizep); return filename__read_str(path, buf, sizep);
} }
int sysfs__read_bool(const char *entry, bool *value)
{
char *buf;
size_t size;
int ret;
ret = sysfs__read_str(entry, &buf, &size);
if (ret < 0)
return ret;
switch (buf[0]) {
case '1':
case 'y':
case 'Y':
*value = true;
break;
case '0':
case 'n':
case 'N':
*value = false;
break;
default:
ret = -1;
}
free(buf);
return ret;
}
int sysctl__read_int(const char *sysctl, int *value) int sysctl__read_int(const char *sysctl, int *value)
{ {
char path[PATH_MAX]; char path[PATH_MAX];
......
...@@ -37,4 +37,5 @@ int sysctl__read_int(const char *sysctl, int *value); ...@@ -37,4 +37,5 @@ int sysctl__read_int(const char *sysctl, int *value);
int sysfs__read_int(const char *entry, int *value); int sysfs__read_int(const char *entry, int *value);
int sysfs__read_ull(const char *entry, unsigned long long *value); int sysfs__read_ull(const char *entry, unsigned long long *value);
int sysfs__read_str(const char *entry, char **buf, size_t *sizep); int sysfs__read_str(const char *entry, char **buf, size_t *sizep);
int sysfs__read_bool(const char *entry, bool *value);
#endif /* __API_FS__ */ #endif /* __API_FS__ */
...@@ -94,8 +94,7 @@ to activate system-wide monitoring. Default is to count on all CPUs. ...@@ -94,8 +94,7 @@ to activate system-wide monitoring. Default is to count on all CPUs.
-A:: -A::
--no-aggr:: --no-aggr::
Do not aggregate counts across all monitored CPUs in system-wide mode (-a). Do not aggregate counts across all monitored CPUs.
This option is only valid in system-wide mode.
-n:: -n::
--null:: --null::
......
...@@ -948,27 +948,29 @@ static int __cmd_record(int argc, const char **argv) ...@@ -948,27 +948,29 @@ static int __cmd_record(int argc, const char **argv)
int cmd_lock(int argc, const char **argv, const char *prefix __maybe_unused) int cmd_lock(int argc, const char **argv, const char *prefix __maybe_unused)
{ {
const struct option info_options[] = {
OPT_BOOLEAN('t', "threads", &info_threads,
"dump thread list in perf.data"),
OPT_BOOLEAN('m', "map", &info_map,
"map of lock instances (address:name table)"),
OPT_BOOLEAN('f', "force", &force, "don't complain, do it"),
OPT_END()
};
const struct option lock_options[] = { const struct option lock_options[] = {
OPT_STRING('i', "input", &input_name, "file", "input file name"), OPT_STRING('i', "input", &input_name, "file", "input file name"),
OPT_INCR('v', "verbose", &verbose, "be more verbose (show symbol address, etc)"), OPT_INCR('v', "verbose", &verbose, "be more verbose (show symbol address, etc)"),
OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace, "dump raw trace in ASCII"), OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace, "dump raw trace in ASCII"),
OPT_BOOLEAN('f', "force", &force, "don't complain, do it"),
OPT_END() OPT_END()
}; };
const struct option info_options[] = {
OPT_BOOLEAN('t', "threads", &info_threads,
"dump thread list in perf.data"),
OPT_BOOLEAN('m', "map", &info_map,
"map of lock instances (address:name table)"),
OPT_PARENT(lock_options)
};
const struct option report_options[] = { const struct option report_options[] = {
OPT_STRING('k', "key", &sort_key, "acquired", OPT_STRING('k', "key", &sort_key, "acquired",
"key for sorting (acquired / contended / avg_wait / wait_total / wait_max / wait_min)"), "key for sorting (acquired / contended / avg_wait / wait_total / wait_max / wait_min)"),
OPT_BOOLEAN('f', "force", &force, "don't complain, do it"),
/* TODO: type */ /* TODO: type */
OPT_END() OPT_PARENT(lock_options)
}; };
const char * const info_usage[] = { const char * const info_usage[] = {
"perf lock info [<options>]", "perf lock info [<options>]",
NULL NULL
......
...@@ -1933,6 +1933,11 @@ int cmd_timechart(int argc, const char **argv, ...@@ -1933,6 +1933,11 @@ int cmd_timechart(int argc, const char **argv,
.merge_dist = 1000, .merge_dist = 1000,
}; };
const char *output_name = "output.svg"; const char *output_name = "output.svg";
const struct option timechart_common_options[] = {
OPT_BOOLEAN('P', "power-only", &tchart.power_only, "output power data only"),
OPT_BOOLEAN('T', "tasks-only", &tchart.tasks_only, "output processes data only"),
OPT_END()
};
const struct option timechart_options[] = { const struct option timechart_options[] = {
OPT_STRING('i', "input", &input_name, "file", "input file name"), OPT_STRING('i', "input", &input_name, "file", "input file name"),
OPT_STRING('o', "output", &output_name, "file", "output file name"), OPT_STRING('o', "output", &output_name, "file", "output file name"),
...@@ -1940,9 +1945,6 @@ int cmd_timechart(int argc, const char **argv, ...@@ -1940,9 +1945,6 @@ int cmd_timechart(int argc, const char **argv,
OPT_CALLBACK(0, "highlight", NULL, "duration or task name", OPT_CALLBACK(0, "highlight", NULL, "duration or task name",
"highlight tasks. Pass duration in ns or process name.", "highlight tasks. Pass duration in ns or process name.",
parse_highlight), parse_highlight),
OPT_BOOLEAN('P', "power-only", &tchart.power_only, "output power data only"),
OPT_BOOLEAN('T', "tasks-only", &tchart.tasks_only,
"output processes data only"),
OPT_CALLBACK('p', "process", NULL, "process", OPT_CALLBACK('p', "process", NULL, "process",
"process selector. Pass a pid or process name.", "process selector. Pass a pid or process name.",
parse_process), parse_process),
...@@ -1962,22 +1964,18 @@ int cmd_timechart(int argc, const char **argv, ...@@ -1962,22 +1964,18 @@ int cmd_timechart(int argc, const char **argv,
"merge events that are merge-dist us apart", "merge events that are merge-dist us apart",
parse_time), parse_time),
OPT_BOOLEAN('f', "force", &tchart.force, "don't complain, do it"), OPT_BOOLEAN('f', "force", &tchart.force, "don't complain, do it"),
OPT_END() OPT_PARENT(timechart_common_options),
}; };
const char * const timechart_subcommands[] = { "record", NULL }; const char * const timechart_subcommands[] = { "record", NULL };
const char *timechart_usage[] = { const char *timechart_usage[] = {
"perf timechart [<options>] {record}", "perf timechart [<options>] {record}",
NULL NULL
}; };
const struct option timechart_record_options[] = { const struct option timechart_record_options[] = {
OPT_BOOLEAN('P', "power-only", &tchart.power_only, "output power data only"),
OPT_BOOLEAN('T', "tasks-only", &tchart.tasks_only,
"output processes data only"),
OPT_BOOLEAN('I', "io-only", &tchart.io_only, OPT_BOOLEAN('I', "io-only", &tchart.io_only,
"record only IO data"), "record only IO data"),
OPT_BOOLEAN('g', "callchain", &tchart.with_backtrace, "record callchain"), OPT_BOOLEAN('g', "callchain", &tchart.with_backtrace, "record callchain"),
OPT_END() OPT_PARENT(timechart_common_options),
}; };
const char * const timechart_record_usage[] = { const char * const timechart_record_usage[] = {
"perf timechart record [<options>]", "perf timechart record [<options>]",
......
...@@ -1288,11 +1288,12 @@ int perf_event__process_exit(struct perf_tool *tool __maybe_unused, ...@@ -1288,11 +1288,12 @@ int perf_event__process_exit(struct perf_tool *tool __maybe_unused,
size_t perf_event__fprintf_aux(union perf_event *event, FILE *fp) size_t perf_event__fprintf_aux(union perf_event *event, FILE *fp)
{ {
return fprintf(fp, " offset: %#"PRIx64" size: %#"PRIx64" flags: %#"PRIx64" [%s%s]\n", return fprintf(fp, " offset: %#"PRIx64" size: %#"PRIx64" flags: %#"PRIx64" [%s%s%s]\n",
event->aux.aux_offset, event->aux.aux_size, event->aux.aux_offset, event->aux.aux_size,
event->aux.flags, event->aux.flags,
event->aux.flags & PERF_AUX_FLAG_TRUNCATED ? "T" : "", event->aux.flags & PERF_AUX_FLAG_TRUNCATED ? "T" : "",
event->aux.flags & PERF_AUX_FLAG_OVERWRITE ? "O" : ""); event->aux.flags & PERF_AUX_FLAG_OVERWRITE ? "O" : "",
event->aux.flags & PERF_AUX_FLAG_PARTIAL ? "P" : "");
} }
size_t perf_event__fprintf_itrace_start(union perf_event *event, FILE *fp) size_t perf_event__fprintf_itrace_start(union perf_event *event, FILE *fp)
......
...@@ -276,6 +276,7 @@ struct events_stats { ...@@ -276,6 +276,7 @@ struct events_stats {
u64 total_lost; u64 total_lost;
u64 total_lost_samples; u64 total_lost_samples;
u64 total_aux_lost; u64 total_aux_lost;
u64 total_aux_partial;
u64 total_invalid_chains; u64 total_invalid_chains;
u32 nr_events[PERF_RECORD_HEADER_MAX]; u32 nr_events[PERF_RECORD_HEADER_MAX];
u32 nr_non_filtered_samples; u32 nr_non_filtered_samples;
......
...@@ -3048,7 +3048,7 @@ concat_probe_trace_events(struct probe_trace_event **tevs, int *ntevs, ...@@ -3048,7 +3048,7 @@ concat_probe_trace_events(struct probe_trace_event **tevs, int *ntevs,
struct probe_trace_event *new_tevs; struct probe_trace_event *new_tevs;
int ret = 0; int ret = 0;
if (ntevs == 0) { if (*ntevs == 0) {
*tevs = *tevs2; *tevs = *tevs2;
*ntevs = ntevs2; *ntevs = ntevs2;
*tevs2 = NULL; *tevs2 = NULL;
......
#include <linux/kernel.h> #include <linux/kernel.h>
#include <traceevent/event-parse.h> #include <traceevent/event-parse.h>
#include <api/fs/fs.h>
#include <byteswap.h> #include <byteswap.h>
#include <unistd.h> #include <unistd.h>
...@@ -1260,9 +1261,12 @@ static int machines__deliver_event(struct machines *machines, ...@@ -1260,9 +1261,12 @@ static int machines__deliver_event(struct machines *machines,
case PERF_RECORD_UNTHROTTLE: case PERF_RECORD_UNTHROTTLE:
return tool->unthrottle(tool, event, sample, machine); return tool->unthrottle(tool, event, sample, machine);
case PERF_RECORD_AUX: case PERF_RECORD_AUX:
if (tool->aux == perf_event__process_aux && if (tool->aux == perf_event__process_aux) {
(event->aux.flags & PERF_AUX_FLAG_TRUNCATED)) if (event->aux.flags & PERF_AUX_FLAG_TRUNCATED)
evlist->stats.total_aux_lost += 1; evlist->stats.total_aux_lost += 1;
if (event->aux.flags & PERF_AUX_FLAG_PARTIAL)
evlist->stats.total_aux_partial += 1;
}
return tool->aux(tool, event, sample, machine); return tool->aux(tool, event, sample, machine);
case PERF_RECORD_ITRACE_START: case PERF_RECORD_ITRACE_START:
return tool->itrace_start(tool, event, sample, machine); return tool->itrace_start(tool, event, sample, machine);
...@@ -1555,6 +1559,23 @@ static void perf_session__warn_about_errors(const struct perf_session *session) ...@@ -1555,6 +1559,23 @@ static void perf_session__warn_about_errors(const struct perf_session *session)
stats->nr_events[PERF_RECORD_AUX]); stats->nr_events[PERF_RECORD_AUX]);
} }
if (session->tool->aux == perf_event__process_aux &&
stats->total_aux_partial != 0) {
bool vmm_exclusive = false;
(void)sysfs__read_bool("module/kvm_intel/parameters/vmm_exclusive",
&vmm_exclusive);
ui__warning("AUX data had gaps in it %" PRIu64 " times out of %u!\n\n"
"Are you running a KVM guest in the background?%s\n\n",
stats->total_aux_partial,
stats->nr_events[PERF_RECORD_AUX],
vmm_exclusive ?
"\nReloading kvm_intel module with vmm_exclusive=0\n"
"will reduce the gaps to only guest's timeslices." :
"");
}
if (stats->nr_unknown_events != 0) { if (stats->nr_unknown_events != 0) {
ui__warning("Found %u unknown events!\n\n" ui__warning("Found %u unknown events!\n\n"
"Is this an older tool processing a perf.data " "Is this an older tool processing a perf.data "
......
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