perf fixes
Subsystems (5)
Linus,
Please pull the latest perf-urgent-for-linus git tree from:
git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git perf-urgent-for-linus
# HEAD: 7579dfc42d2e3bef901991803efc81dc1ac65f2b Merge tag 'perf-urgent-for-mingo-5.1-20190419' of git://git.kernel.org/pub/scm/linux/kernel/git/acme/linux into perf/urgent
Misc fixes:
- various tooling fixes
- kretprobe fixes
- kprobes annotation fixes
- kprobes error checking fix
- fix the default events for AMD Family 17h CPUs
- PEBS fix
- AUX record fix
- address filtering fix
Thanks,
Ingo
------------------>
Adrian Hunter (1):
perf scripts python: export-to-sqlite.py: Fix use of parent_id in calls_view
Alexander Shishkin (2):
perf/core: Fix the address filtering fix perf/ring_buffer: Fix AUX record suppression
Arnaldo Carvalho de Melo (1):
tools include uapi: Sync sound/asound.h copy
Gustavo A. R. Silva (1):
perf header: Fix lock/unlock imbalances when processing BPF/BTF info
Jiri Olsa (6):
perf stat: Disable DIR_FORMAT feature for 'perf stat record'
perf top: Always sample time to satisfy needs of use of ordered queuing perf bpf: Return NULL when RB tree lookup fails in perf_env__find_bpf_prog_info() perf evlist: Fix side band thread draining
perf tools: Fix map reference counting
perf bpf: Return NULL when RB tree lookup fails in perf_env__find_btf()
Kan Liang (1):
perf/x86: Fix incorrect PEBS_REGS
Kim Phillips (1):
perf/x86/amd: Add event map for AMD Family 17h
Mao Han (1):
perf evsel: Use hweight64() instead of hweight_long(attr.sample_regs_user)
Masami Hiramatsu (4):
kprobes: Fix error check when reusing optimized probes x86/kprobes: Verify stack frame on kretprobe
kprobes: Mark ftrace mcount handler functions nokprobe x86/kprobes: Avoid kretprobe recursion bug
Rikard Falkeborn (1):
tools lib traceevent: Fix missing equality check for strcmp
Song Liu (1):
perf tools: Check maps for bpf programs
arch/x86/events/amd/core.c | 35 ++++++++++++++----- arch/x86/events/intel/core.c | 2 +- arch/x86/events/perf_event.h | 38 ++++++++++----------- arch/x86/kernel/kprobes/core.c | 48 +++++++++++++++++++++++++-- include/linux/kprobes.h | 1 + kernel/events/core.c | 37 ++++++++++++--------- kernel/events/ring_buffer.c | 33 +++++++++--------- kernel/kprobes.c | 6 ++-- kernel/trace/ftrace.c | 6 +++- tools/include/uapi/sound/asound.h | 1 + tools/lib/traceevent/event-parse.c | 2 +- tools/perf/builtin-stat.c | 1 + tools/perf/builtin-top.c | 1 + tools/perf/scripts/python/export-to-sqlite.py | 2 +- tools/perf/util/env.c | 8 +++-- tools/perf/util/evlist.c | 14 +++++--- tools/perf/util/evsel.c | 12 +++---- tools/perf/util/header.c | 22 +++++++----- tools/perf/util/map.c | 20 +++++++++-- tools/perf/util/map.h | 4 ++- 20 files changed, 195 insertions(+), 98 deletions(-)
arch/x86/events/amd/core.c +26 -9
@@ -117,22 +117,39 @@ static __initconst const u64 amd_hw_cache_event_ids };/* -* AMD Performance Monitor K7 and later. +* AMD Performance Monitor K7 and later, up to and including Family 16h: */ static const u64 amd_perfmon_event_map[PERF_COUNT_HW_MAX ] = { - [PERF_COUNT_HW_CPU_CYCLES ] =0x0076 , - [PERF_COUNT_HW_INSTRUCTIONS ] =0x00c0 , - [PERF_COUNT_HW_CACHE_REFERENCES ] =0x077d , - [PERF_COUNT_HW_CACHE_MISSES ] =0x077e , - [PERF_COUNT_HW_BRANCH_INSTRUCTIONS ] =0x00c2 , - [PERF_COUNT_HW_BRANCH_MISSES ] =0x00c3 , - [PERF_COUNT_HW_STALLED_CYCLES_FRONTEND ] =0x00d0 ,/* "Decoder empty" event */ - [PERF_COUNT_HW_STALLED_CYCLES_BACKEND ] =0x00d1 ,/* "Dispatch stalls" event */ + [PERF_COUNT_HW_CPU_CYCLES ] =0x0076 , + [PERF_COUNT_HW_INSTRUCTIONS ] =0x00c0 , + [PERF_COUNT_HW_CACHE_REFERENCES ] =0x077d , + [PERF_COUNT_HW_CACHE_MISSES ] =0x077e , + [PERF_COUNT_HW_BRANCH_INSTRUCTIONS ] =0x00c2 , + [PERF_COUNT_HW_BRANCH_MISSES ] =0x00c3 , + [PERF_COUNT_HW_STALLED_CYCLES_FRONTEND ] =0x00d0 ,/* "Decoder empty" event */ + [PERF_COUNT_HW_STALLED_CYCLES_BACKEND ] =0x00d1 ,/* "Dispatch stalls" event */ +}; + +/* +* AMD Performance Monitor Family 17h and later: +*/ +static const u64 amd_f17h_perfmon_event_map[PERF_COUNT_HW_MAX ] = +{ + [PERF_COUNT_HW_CPU_CYCLES ] =0x0076 , + [PERF_COUNT_HW_INSTRUCTIONS ] =0x00c0 , + [PERF_COUNT_HW_CACHE_REFERENCES ] =0xff60 , + [PERF_COUNT_HW_BRANCH_INSTRUCTIONS ] =0x00c2 , + [PERF_COUNT_HW_BRANCH_MISSES ] =0x00c3 , + [PERF_COUNT_HW_STALLED_CYCLES_FRONTEND ] =0x0287 , + [PERF_COUNT_HW_STALLED_CYCLES_BACKEND ] =0x0187 , };static u64 amd_pmu_event_map (int hw_event) { +if (boot_cpu_data.x86 >=0x17 ) +return amd_f17h_perfmon_event_map[hw_event]; +return amd_perfmon_event_map[hw_event]; }
arch/x86/events/intel/core.c +1 -1
@@ -3131,7 +3131,7 @@ static unsigned long intel_pmu_large_pebs_flags(struct perf_event *event) flags &= ~PERF_SAMPLE_TIME ;if (!event->attr.exclude_kernel) flags &= ~PERF_SAMPLE_REGS_USER ; -if (event->attr.sample_regs_user & ~PEBS_REGS ) +if (event->attr.sample_regs_user & ~PEBS_GP_REGS ) flags &= ~(PERF_SAMPLE_REGS_USER |PERF_SAMPLE_REGS_INTR );return flags; }
arch/x86/events/perf_event.h +19 -19
@@ -96,25 +96,25 @@ struct amd_nb { PERF_SAMPLE_REGS_INTR | PERF_SAMPLE_REGS_USER | \ PERF_SAMPLE_PERIOD) -#define PEBS_REGS \ - (PERF_REG_X86_AX | \ - PERF_REG_X86_BX | \ - PERF_REG_X86_CX | \ - PERF_REG_X86_DX | \ - PERF_REG_X86_DI | \ - PERF_REG_X86_SI | \ - PERF_REG_X86_SP | \ - PERF_REG_X86_BP | \ - PERF_REG_X86_IP | \ - PERF_REG_X86_FLAGS | \ - PERF_REG_X86_R8 | \ - PERF_REG_X86_R9 | \ - PERF_REG_X86_R10 | \ - PERF_REG_X86_R11 | \ - PERF_REG_X86_R12 | \ - PERF_REG_X86_R13 | \ - PERF_REG_X86_R14 | \ - PERF_REG_X86_R15) +#define PEBS_GP_REGS \ + ((1ULL << PERF_REG_X86_AX) | \ + (1ULL << PERF_REG_X86_BX) | \ + (1ULL << PERF_REG_X86_CX) | \ + (1ULL << PERF_REG_X86_DX) | \ + (1ULL << PERF_REG_X86_DI) | \ + (1ULL << PERF_REG_X86_SI) | \ + (1ULL << PERF_REG_X86_SP) | \ + (1ULL << PERF_REG_X86_BP) | \ + (1ULL << PERF_REG_X86_IP) | \ + (1ULL << PERF_REG_X86_FLAGS) | \ + (1ULL << PERF_REG_X86_R8) | \ + (1ULL << PERF_REG_X86_R9) | \ + (1ULL << PERF_REG_X86_R10) | \ + (1ULL << PERF_REG_X86_R11) | \ + (1ULL << PERF_REG_X86_R12) | \ + (1ULL << PERF_REG_X86_R13) | \ + (1ULL << PERF_REG_X86_R14) | \ + (1ULL << PERF_REG_X86_R15))/* * Per register state.
arch/x86/kernel/kprobes/core.c +46 -2
106 lines not on this page · raw message
include/linux/kprobes.h +1 -0
@@ -173,6 +173,7 @@ struct kretprobe_instance {struct kretprobe *rp;kprobe_opcode_t *ret_addr;struct task_struct *task; +void *fp;char data[0 ]; };
kernel/events/core.c +21 -16
@@ -9077,26 +9077,29 @@ static void perf_event_addr_filters_apply(struct perf_event *event)if (task ==TASK_TOMBSTONE )return ; -if (!ifh->nr_file_filters) -return ; - - mm =get_task_mm (event->ctx->task); -if (!mm) - goto restart; +if (ifh->nr_file_filters) { + mm =get_task_mm (event->ctx->task); +if (!mm) + goto restart; -down_read (&mm->mmap_sem); +down_read (&mm->mmap_sem); + }raw_spin_lock_irqsave (&ifh->lock, flags);list_for_each_entry (filter, &ifh->list, entry) { - event->addr_filter_ranges[count].start =0 ; - event->addr_filter_ranges[count].size =0 ; +if (filter->path.dentry) { +/* +* Adjust base offset if the filter is associated to a +* binary that needs to be mapped: +*/ + event->addr_filter_ranges[count].start =0 ; + event->addr_filter_ranges[count].size =0 ; -/* -* Adjust base offset if the filter is associated to a binary -* that needs to be mapped: -*/ -if (filter->path.dentry)perf_addr_filter_apply (filter, mm, &event->addr_filter_ranges[count]); + }else { + event->addr_filter_ranges[count].start = filter->offset; + event->addr_filter_ranges[count].size = filter->size; + } count++; }
@@ -9104,9 +9107,11 @@ static void perf_event_addr_filters_apply(struct perf_event *event) event->addr_filters_gen++;raw_spin_unlock_irqrestore (&ifh->lock, flags); -up_read (&mm->mmap_sem); +if (ifh->nr_file_filters) { +up_read (&mm->mmap_sem); -mmput (mm); +mmput (mm); + } restart:perf_event_stop (event,1 );
kernel/events/ring_buffer.c +15 -18
@@ -455,24 +455,21 @@ void perf_aux_output_end(struct perf_output_handle *handle, unsigned long size) rb->aux_head += size; } -if (size || handle->aux_flags) { -/* -* Only send RECORD_AUX if we have something useful to communicate -* -* Note: the OVERWRITE records by themselves are not considered -* useful, as they don't communicate any *new* information, -* aside from the short-lived offset, that becomes history at -* the next event sched-in and therefore isn't useful. -* The userspace that needs to copy out AUX data in overwrite -* mode should know to use user_page::aux_head for the actual -* offset. So, from now on we don't output AUX records that -* have *only* OVERWRITE flag set. -*/ - -if (handle->aux_flags & ~(u64 )PERF_AUX_FLAG_OVERWRITE ) -perf_event_aux_event (handle->event, aux_head, size, - handle->aux_flags); - } +/* +* Only send RECORD_AUX if we have something useful to communicate +* +* Note: the OVERWRITE records by themselves are not considered +* useful, as they don't communicate any *new* information, +* aside from the short-lived offset, that becomes history at +* the next event sched-in and therefore isn't useful. +* The userspace that needs to copy out AUX data in overwrite +* mode should know to use user_page::aux_head for the actual +* offset. So, from now on we don't output AUX records that +* have *only* OVERWRITE flag set. +*/ +if (size || (handle->aux_flags & ~(u64 )PERF_AUX_FLAG_OVERWRITE )) +perf_event_aux_event (handle->event, aux_head, size, + handle->aux_flags); rb->user_page->aux_head = rb->aux_head;if (rb_need_aux_wakeup (rb))
kernel/kprobes.c +2 -4
@@ -709,7 +709,6 @@ static void unoptimize_kprobe(struct kprobe *p, bool force)static int reuse_unused_kprobe (struct kprobe *ap) {struct optimized_kprobe *op; -int ret;/* * Unused kprobe MUST be on the way of delayed unoptimizing (means
@@ -720,9 +719,8 @@ static int reuse_unused_kprobe(struct kprobe *ap)/* Enable the probe again */ ap->flags &= ~KPROBE_FLAG_DISABLED ;/* Optimize it again (remove from op->list) */ - ret =kprobe_optready (ap); -if (ret) -return ret; +if (!kprobe_optready (ap)) +return -EINVAL ;optimize_kprobe (ap);return 0 ;
kernel/trace/ftrace.c +5 -1
@@ -33,6 +33,7 @@#include <linux/list.h> #include <linux/hash.h> #include <linux/rcupdate.h> +#include <linux/kprobes.h> #include <trace/events/sched.h>
@@ -6246,7 +6247,7 @@ void ftrace_reset_array_ops(struct trace_array *tr) tr->ops->func = ftrace_stub; } -static inline void +static nokprobe_inline void__ftrace_ops_list_func (unsigned long ip,unsigned long parent_ip,struct ftrace_ops *ignored,struct pt_regs *regs) {
@@ -6306,11 +6307,13 @@ static void ftrace_ops_list_func(unsigned long ip, unsigned long parent_ip, {__ftrace_ops_list_func (ip, parent_ip,NULL , regs); } +NOKPROBE_SYMBOL (ftrace_ops_list_func);#else static void ftrace_ops_no_ops (unsigned long ip,unsigned long parent_ip) {__ftrace_ops_list_func (ip, parent_ip,NULL ,NULL ); } +NOKPROBE_SYMBOL (ftrace_ops_no_ops);#endif /*
@@ -6337,6 +6340,7 @@ static void ftrace_ops_assist_func(unsigned long ip, unsigned long parent_ip,preempt_enable_notrace ();trace_clear_recursion (bit); } +NOKPROBE_SYMBOL (ftrace_ops_assist_func);/** * ftrace_ops_get_func - get the function a trampoline should call
tools/include/uapi/sound/asound.h +1 -0
@@ -32,6 +32,7 @@#ifndef __KERNEL__#include <stdlib.h> +#include <time.h> #endif /*
tools/lib/traceevent/event-parse.c +1 -1
@@ -2233,7 +2233,7 @@ eval_type_str(unsigned long long val, const char *type, int pointer)return val &0xffffffff ;if (strcmp (type,"u64" ) ==0 || -strcmp (type,"s64" )) +strcmp (type,"s64" ) ==0 )return val;if (strcmp (type,"s8" ) ==0 )
tools/perf/builtin-stat.c +1 -0
@@ -1308,6 +1308,7 @@ static void init_features(struct perf_session *session)for (feat =HEADER_FIRST_FEATURE ; feat <HEADER_LAST_FEATURE ; feat++)perf_header__set_feat (&session->header, feat); +perf_header__clear_feat (&session->header,HEADER_DIR_FORMAT );perf_header__clear_feat (&session->header,HEADER_BUILD_ID );perf_header__clear_feat (&session->header,HEADER_TRACING_DATA );perf_header__clear_feat (&session->header,HEADER_BRANCH_STACK );
tools/perf/builtin-top.c +1 -0
@@ -1377,6 +1377,7 @@ int cmd_top(int argc, const char **argv)* */ .overwrite = 0, .sample_time = true, + .sample_time_set = true, }, .max_stack = sysctl__max_stack(), .annotation_opts = annotation__default_options,
tools/perf/scripts/python/export-to-sqlite.py +1 -1
@@ -331,7 +331,7 @@ if perf_db_export_calls: 'return_id,' 'CASE WHEN flags=0 THEN \'\' WHEN flags=1 THEN \'no call\' WHEN flags=2 THEN \'no return\' WHEN flags=3 THEN \'no call/return\' WHEN flags=6 THEN \'jump\' ELSE flags END AS flags,' 'parent_call_path_id,' - 'parent_id' + 'calls.parent_id' ' FROM calls INNER JOIN call_paths ON call_paths.id = call_path_id') do_query(query, 'CREATE VIEW samples_view AS '
tools/perf/util/env.c +6 -2
@@ -57,9 +57,11 @@ struct bpf_prog_info_node *perf_env__find_bpf_prog_info(struct perf_env *env,else if (prog_id > node->info_linear->info.id) n = n->rb_right;else -break ; + goto out; } + node =NULL ; +out:up_read (&env->bpf_progs.lock);return node; }
@@ -109,10 +111,12 @@ struct btf_node *perf_env__find_btf(struct perf_env *env, __u32 btf_id)else if (btf_id > node->id) n = n->rb_right;else -break ; + goto out; } + node =NULL ;up_read (&env->bpf_progs.lock); +out:return node; }
tools/perf/util/evlist.c +9 -5
@@ -1868,12 +1868,12 @@ static void *perf_evlist__poll_thread(void *arg) {struct perf_evlist *evlist = arg;bool draining = false; -int i; +int i, done =0 ; + +while (!done) { +bool got_data = false; -while (draining || !(evlist->thread.done)) { -if (draining) - draining = false; -else if (evlist->thread.done) +if (evlist->thread.done) draining = true;if (!draining)
@@ -1894,9 +1894,13 @@ static void *perf_evlist__poll_thread(void *arg)pr_warning ("cannot locate proper evsel for the side band event\n" );perf_mmap__consume (map); + got_data = true; }perf_mmap__read_done (map); } + +if (draining && !got_data) +break ; }return NULL ; }
tools/perf/util/evsel.c +6 -6
@@ -2368,7 +2368,7 @@ int perf_evsel__parse_sample(struct perf_evsel *evsel, union perf_event *event,if (data->user_regs.abi) {u64 mask = evsel->attr.sample_regs_user; - sz =hweight_long (mask) *sizeof (u64); + sz =hweight64 (mask) *sizeof (u64);OVERFLOW_CHECK (array, sz, max_size); data->user_regs.mask = mask; data->user_regs.regs = (u64 *)array;
@@ -2424,7 +2424,7 @@ int perf_evsel__parse_sample(struct perf_evsel *evsel, union perf_event *event,if (data->intr_regs.abi !=PERF_SAMPLE_REGS_ABI_NONE ) {u64 mask = evsel->attr.sample_regs_intr; - sz =hweight_long (mask) *sizeof (u64); + sz =hweight64 (mask) *sizeof (u64);OVERFLOW_CHECK (array, sz, max_size); data->intr_regs.mask = mask; data->intr_regs.regs = (u64 *)array;
@@ -2552,7 +2552,7 @@ size_t perf_event__sample_event_size(const struct perf_sample *sample, u64 type,if (type &PERF_SAMPLE_REGS_USER ) {if (sample->user_regs.abi) { result +=sizeof (u64); - sz =hweight_long (sample->user_regs.mask) *sizeof (u64); + sz =hweight64 (sample->user_regs.mask) *sizeof (u64); result += sz; }else { result +=sizeof (u64);
@@ -2580,7 +2580,7 @@ size_t perf_event__sample_event_size(const struct perf_sample *sample, u64 type,if (type &PERF_SAMPLE_REGS_INTR ) {if (sample->intr_regs.abi) { result +=sizeof (u64); - sz =hweight_long (sample->intr_regs.mask) *sizeof (u64); + sz =hweight64 (sample->intr_regs.mask) *sizeof (u64); result += sz; }else { result +=sizeof (u64);
@@ -2710,7 +2710,7 @@ int perf_event__synthesize_sample(union perf_event *event, u64 type,if (type &PERF_SAMPLE_REGS_USER ) {if (sample->user_regs.abi) { *array++ = sample->user_regs.abi; - sz =hweight_long (sample->user_regs.mask) *sizeof (u64); + sz =hweight64 (sample->user_regs.mask) *sizeof (u64);memcpy (array, sample->user_regs.regs, sz); array = (void *)array + sz; }else {
@@ -2746,7 +2746,7 @@ int perf_event__synthesize_sample(union perf_event *event, u64 type,if (type &PERF_SAMPLE_REGS_INTR ) {if (sample->intr_regs.abi) { *array++ = sample->intr_regs.abi; - sz =hweight_long (sample->intr_regs.mask) *sizeof (u64); + sz =hweight64 (sample->intr_regs.mask) *sizeof (u64);memcpy (array, sample->intr_regs.regs, sz); array = (void *)array + sz; }else {
tools/perf/util/header.c +13 -9
@@ -2606,6 +2606,7 @@ static int process_bpf_prog_info(struct feat_fd *ff, void *data __maybe_unused)perf_env__insert_bpf_prog_info (env, info_node); } +up_write (&env->bpf_progs.lock);return 0 ; out:free (info_linear);
@@ -2623,7 +2624,9 @@ static int process_bpf_prog_info(struct feat_fd *ff __maybe_unused, void *data _static int process_bpf_btf (struct feat_fd *ff,void *data__maybe_unused ) {struct perf_env *env = &ff->ph->env; +struct btf_node *node =NULL ;u32 count, i; +int err =-1 ;if (ff->ph->needs_swap) {pr_warning ("interpreting btf from systems with endianity is not yet supported\n" );
@@ -2636,31 +2639,32 @@ static int process_bpf_btf(struct feat_fd *ff, void *data __maybe_unused)down_write (&env->bpf_progs.lock);for (i =0 ; i < count; ++i) { -struct btf_node *node;u32 id, data_size;if (do_read_u32 (ff, &id)) -return -1 ; + goto out;if (do_read_u32 (ff, &data_size)) -return -1 ; + goto out; node =malloc (sizeof (struct btf_node ) + data_size);if (!node) -return -1 ; + goto out; node->id = id; node->data_size = data_size; -if (__do_read (ff, node->data, data_size)) { -free (node); -return -1 ; - } +if (__do_read (ff, node->data, data_size)) + goto out;perf_env__insert_btf (env, node); + node =NULL ; } + err =0 ; +out:up_write (&env->bpf_progs.lock); -return 0 ; +free (node); +return err; }struct feature_ops {
tools/perf/util/map.c +17 -3
@@ -261,6 +261,22 @@ bool __map__is_extra_kernel_map(const struct map *map)return kmap && kmap->name[0 ]; } +bool __map__is_bpf_prog (const struct map *map) +{ +const char *name; + +if (map->dso->binary_type ==DSO_BINARY_TYPE__BPF_PROG_INFO ) +return true; + +/* +* If PERF_RECORD_BPF_EVENT is not included, the dso will not have +* type of DSO_BINARY_TYPE__BPF_PROG_INFO. In such cases, we can +* guess the type based on name. +*/ + name = map->dso->short_name; +return name && (strstr (name,"bpf_prog_" ) == name); +} +bool map__has_symbols (const struct map *map) {return dso__has_symbols (map->dso);
@@ -910,10 +926,8 @@ static void __maps__insert_name(struct maps *maps, struct map *map) rc =strcmp (m->dso->short_name, map->dso->short_name);if (rc <0 ) p = &(*p)->rb_left; -else if (rc >0 ) - p = &(*p)->rb_right;else -return ; + p = &(*p)->rb_right; }rb_link_node (&map->rb_node_name, parent, p);rb_insert_color (&map->rb_node_name, &maps->names);
tools/perf/util/map.h +3 -1
@@ -159,10 +159,12 @@ int map__set_kallsyms_ref_reloc_sym(struct map *map, const char *symbol_name,bool __map__is_kernel (const struct map *map);bool __map__is_extra_kernel_map (const struct map *map); +bool __map__is_bpf_prog (const struct map *map);static inline bool __map__is_kmodule (const struct map *map) { -return !__map__is_kernel (map) && !__map__is_extra_kernel_map (map); +return !__map__is_kernel (map) && !__map__is_extra_kernel_map (map) && + !__map__is_bpf_prog (map); }bool map__has_symbols (const struct map *map);
The pull request you sent on Sat, 20 Apr 2019 09:43:53 +0200:
The pull request you sent on Sat, 20 Apr 2019 09:43:53 +0200:
Quoted 1 line
git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.gitperf-urgent-for-linus
has been merged into torvalds/linux.git:
https://git.kernel.org/torvalds/c/b25c69b9d5e41159b54ad7cb33f7d9ead8523d33
Thank you!
Signature · 2 lines
Deet-doot-dot, I am a bot. https://korg.wiki.kernel.org/userdoc/prtracker