2009-02-24 23:21:36 +08:00
|
|
|
/*
|
|
|
|
* event tracer
|
|
|
|
*
|
|
|
|
* Copyright (C) 2008 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/debugfs.h>
|
|
|
|
#include <linux/uaccess.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/ctype.h>
|
|
|
|
|
tracing: add raw trace point recording infrastructure
Impact: lower overhead tracing
The current event tracer can automatically pick up trace points
that are registered with the TRACE_FORMAT macro. But it required
a printf format string and parsing. Although, this adds the ability
to get guaranteed information like task names and such, it took
a hit in overhead processing. This processing can add about 500-1000
nanoseconds overhead, but in some cases that too is considered
too much and we want to shave off as much from this overhead as
possible.
Tom Zanussi recently posted tracing patches to lkml that are based
on a nice idea about capturing the data via C structs using
STRUCT_ENTER, STRUCT_EXIT type of macros.
I liked that method very much, but did not like the implementation
that required a developer to add data/code in several disjoint
locations.
This patch extends the event_tracer macros to do a similar "raw C"
approach that Tom Zanussi did. But instead of having the developers
needing to tweak a bunch of code all over the place, they can do it
all in one macro - preferably placed near the code that it is
tracing. That makes it much more likely that tracepoints will be
maintained on an ongoing basis by the code they modify.
The new macro TRACE_EVENT_FORMAT is created for this approach. (Note,
a developer may still utilize the more low level DECLARE_TRACE macros
if they don't care about getting their traces automatically in the event
tracer.)
They can also use the existing TRACE_FORMAT if they don't need to code
the tracepoint in C, but just want to use the convenience of printf.
So if the developer wants to "hardwire" a tracepoint in the fastest
possible way, and wants to acquire their data via a user space utility
in a raw binary format, or wants to see it in the trace output but not
sacrifice any performance, then they can implement the faster but
more complex TRACE_EVENT_FORMAT macro.
Here's what usage looks like:
TRACE_EVENT_FORMAT(name,
TPPROTO(proto),
TPARGS(args),
TPFMT(fmt, fmt_args),
TRACE_STUCT(
TRACE_FIELD(type1, item1, assign1)
TRACE_FIELD(type2, item2, assign2)
[...]
),
TPRAWFMT(raw_fmt)
);
Note name, proto, args, and fmt, are all identical to what TRACE_FORMAT
uses.
name: is the unique identifier of the trace point
proto: The proto type that the trace point uses
args: the args in the proto type
fmt: printf format to use with the event printf tracer
fmt_args: the printf argments to match fmt
TRACE_STRUCT starts the ability to create a structure.
Each item in the structure is defined with a TRACE_FIELD
TRACE_FIELD(type, item, assign)
type: the C type of item.
item: the name of the item in the stucture
assign: what to assign the item in the trace point callback
raw_fmt is a way to pretty print the struct. It must match
the order of the items are added in TRACE_STUCT
An example of this would be:
TRACE_EVENT_FORMAT(sched_wakeup,
TPPROTO(struct rq *rq, struct task_struct *p, int success),
TPARGS(rq, p, success),
TPFMT("task %s:%d %s",
p->comm, p->pid, success?"succeeded":"failed"),
TRACE_STRUCT(
TRACE_FIELD(pid_t, pid, p->pid)
TRACE_FIELD(int, success, success)
),
TPRAWFMT("task %d success=%d")
);
This creates us a unique struct of:
struct {
pid_t pid;
int success;
};
And the way the call back would assign these values would be:
entry->pid = p->pid;
entry->success = success;
The nice part about this is that the creation of the assignent is done
via macro magic in the event tracer. Once the TRACE_EVENT_FORMAT is
created, the developer will then have a faster method to record
into the ring buffer. They do not need to worry about the tracer itself.
The developer would only need to touch the files in include/trace/*.h
Again, I would like to give special thanks to Tom Zanussi for this
nice idea.
Idea-from: Tom Zanussi <tzanussi@gmail.com>
Signed-off-by: Steven Rostedt <srostedt@redhat.com>
2009-02-28 08:12:30 +08:00
|
|
|
#include "trace.h"
|
2009-02-24 23:21:36 +08:00
|
|
|
|
2009-02-28 12:32:58 +08:00
|
|
|
#define TRACE_SYSTEM "TRACE_SYSTEM"
|
|
|
|
|
2009-02-25 03:15:08 +08:00
|
|
|
#define events_for_each(event) \
|
|
|
|
for (event = __start_ftrace_events; \
|
|
|
|
(unsigned long)event < (unsigned long)__stop_ftrace_events; \
|
|
|
|
event++)
|
|
|
|
|
2009-02-24 23:21:36 +08:00
|
|
|
void event_trace_printk(unsigned long ip, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
tracing_record_cmdline(current);
|
|
|
|
trace_vprintk(ip, task_curr_ret_stack(current), fmt, ap);
|
|
|
|
va_end(ap);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ftrace_clear_events(void)
|
|
|
|
{
|
|
|
|
struct ftrace_event_call *call = (void *)__start_ftrace_events;
|
|
|
|
|
|
|
|
|
|
|
|
while ((unsigned long)call < (unsigned long)__stop_ftrace_events) {
|
|
|
|
|
|
|
|
if (call->enabled) {
|
|
|
|
call->enabled = 0;
|
|
|
|
call->unregfunc();
|
|
|
|
}
|
|
|
|
call++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ftrace_set_clr_event(char *buf, int set)
|
|
|
|
{
|
2009-02-25 03:15:08 +08:00
|
|
|
struct ftrace_event_call *call = __start_ftrace_events;
|
2009-02-28 12:32:58 +08:00
|
|
|
char *event = NULL, *sub = NULL, *match;
|
|
|
|
int ret = -EINVAL;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The buf format can be <subsystem>:<event-name>
|
|
|
|
* *:<event-name> means any event by that name.
|
|
|
|
* :<event-name> is the same.
|
|
|
|
*
|
|
|
|
* <subsystem>:* means all events in that subsystem
|
|
|
|
* <subsystem>: means the same.
|
|
|
|
*
|
|
|
|
* <name> (no ':') means all events in a subsystem with
|
|
|
|
* the name <name> or any event that matches <name>
|
|
|
|
*/
|
|
|
|
|
|
|
|
match = strsep(&buf, ":");
|
|
|
|
if (buf) {
|
|
|
|
sub = match;
|
|
|
|
event = buf;
|
|
|
|
match = NULL;
|
|
|
|
|
|
|
|
if (!strlen(sub) || strcmp(sub, "*") == 0)
|
|
|
|
sub = NULL;
|
|
|
|
if (!strlen(event) || strcmp(event, "*") == 0)
|
|
|
|
event = NULL;
|
|
|
|
}
|
2009-02-24 23:21:36 +08:00
|
|
|
|
2009-02-25 03:15:08 +08:00
|
|
|
events_for_each(call) {
|
2009-02-24 23:21:36 +08:00
|
|
|
|
2009-02-25 03:15:08 +08:00
|
|
|
if (!call->name)
|
|
|
|
continue;
|
|
|
|
|
2009-02-28 12:32:58 +08:00
|
|
|
if (match &&
|
|
|
|
strcmp(match, call->name) != 0 &&
|
|
|
|
strcmp(match, call->system) != 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (sub && strcmp(sub, call->system) != 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (event && strcmp(event, call->name) != 0)
|
2009-02-24 23:21:36 +08:00
|
|
|
continue;
|
|
|
|
|
|
|
|
if (set) {
|
|
|
|
/* Already set? */
|
|
|
|
if (call->enabled)
|
|
|
|
return 0;
|
|
|
|
call->enabled = 1;
|
|
|
|
call->regfunc();
|
|
|
|
} else {
|
|
|
|
/* Already cleared? */
|
|
|
|
if (!call->enabled)
|
|
|
|
return 0;
|
|
|
|
call->enabled = 0;
|
|
|
|
call->unregfunc();
|
|
|
|
}
|
2009-02-28 12:32:58 +08:00
|
|
|
ret = 0;
|
2009-02-24 23:21:36 +08:00
|
|
|
}
|
2009-02-28 12:32:58 +08:00
|
|
|
return ret;
|
2009-02-24 23:21:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* 128 should be much more than enough */
|
|
|
|
#define EVENT_BUF_SIZE 127
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
ftrace_event_write(struct file *file, const char __user *ubuf,
|
|
|
|
size_t cnt, loff_t *ppos)
|
|
|
|
{
|
|
|
|
size_t read = 0;
|
|
|
|
int i, set = 1;
|
|
|
|
ssize_t ret;
|
|
|
|
char *buf;
|
|
|
|
char ch;
|
|
|
|
|
|
|
|
if (!cnt || cnt < 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
ret = get_user(ch, ubuf++);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
read++;
|
|
|
|
cnt--;
|
|
|
|
|
|
|
|
/* skip white space */
|
|
|
|
while (cnt && isspace(ch)) {
|
|
|
|
ret = get_user(ch, ubuf++);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
read++;
|
|
|
|
cnt--;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Only white space found? */
|
|
|
|
if (isspace(ch)) {
|
|
|
|
file->f_pos += read;
|
|
|
|
ret = read;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
buf = kmalloc(EVENT_BUF_SIZE+1, GFP_KERNEL);
|
|
|
|
if (!buf)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
if (cnt > EVENT_BUF_SIZE)
|
|
|
|
cnt = EVENT_BUF_SIZE;
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
while (cnt && !isspace(ch)) {
|
|
|
|
if (!i && ch == '!')
|
|
|
|
set = 0;
|
|
|
|
else
|
|
|
|
buf[i++] = ch;
|
|
|
|
|
|
|
|
ret = get_user(ch, ubuf++);
|
|
|
|
if (ret)
|
|
|
|
goto out_free;
|
|
|
|
read++;
|
|
|
|
cnt--;
|
|
|
|
}
|
|
|
|
buf[i] = 0;
|
|
|
|
|
|
|
|
file->f_pos += read;
|
|
|
|
|
|
|
|
ret = ftrace_set_clr_event(buf, set);
|
|
|
|
if (ret)
|
|
|
|
goto out_free;
|
|
|
|
|
|
|
|
ret = read;
|
|
|
|
|
|
|
|
out_free:
|
|
|
|
kfree(buf);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void *
|
|
|
|
t_next(struct seq_file *m, void *v, loff_t *pos)
|
|
|
|
{
|
|
|
|
struct ftrace_event_call *call = m->private;
|
|
|
|
struct ftrace_event_call *next = call;
|
|
|
|
|
|
|
|
(*pos)++;
|
|
|
|
|
|
|
|
if ((unsigned long)call >= (unsigned long)__stop_ftrace_events)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
m->private = ++next;
|
|
|
|
|
|
|
|
return call;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void *t_start(struct seq_file *m, loff_t *pos)
|
|
|
|
{
|
|
|
|
return t_next(m, NULL, pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void *
|
|
|
|
s_next(struct seq_file *m, void *v, loff_t *pos)
|
|
|
|
{
|
|
|
|
struct ftrace_event_call *call = m->private;
|
|
|
|
struct ftrace_event_call *next;
|
|
|
|
|
|
|
|
(*pos)++;
|
|
|
|
|
|
|
|
retry:
|
|
|
|
if ((unsigned long)call >= (unsigned long)__stop_ftrace_events)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (!call->enabled) {
|
|
|
|
call++;
|
|
|
|
goto retry;
|
|
|
|
}
|
|
|
|
|
|
|
|
next = call;
|
|
|
|
m->private = ++next;
|
|
|
|
|
|
|
|
return call;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void *s_start(struct seq_file *m, loff_t *pos)
|
|
|
|
{
|
|
|
|
return s_next(m, NULL, pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int t_show(struct seq_file *m, void *v)
|
|
|
|
{
|
|
|
|
struct ftrace_event_call *call = v;
|
|
|
|
|
2009-02-28 12:32:58 +08:00
|
|
|
if (strcmp(call->system, TRACE_SYSTEM) != 0)
|
|
|
|
seq_printf(m, "%s:", call->system);
|
2009-02-24 23:21:36 +08:00
|
|
|
seq_printf(m, "%s\n", call->name);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void t_stop(struct seq_file *m, void *p)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
ftrace_event_seq_open(struct inode *inode, struct file *file)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
const struct seq_operations *seq_ops;
|
|
|
|
|
|
|
|
if ((file->f_mode & FMODE_WRITE) &&
|
|
|
|
!(file->f_flags & O_APPEND))
|
|
|
|
ftrace_clear_events();
|
|
|
|
|
|
|
|
seq_ops = inode->i_private;
|
|
|
|
ret = seq_open(file, seq_ops);
|
|
|
|
if (!ret) {
|
|
|
|
struct seq_file *m = file->private_data;
|
|
|
|
|
|
|
|
m->private = __start_ftrace_events;
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2009-02-25 03:15:08 +08:00
|
|
|
static ssize_t
|
|
|
|
event_enable_read(struct file *filp, char __user *ubuf, size_t cnt,
|
|
|
|
loff_t *ppos)
|
|
|
|
{
|
|
|
|
struct ftrace_event_call *call = filp->private_data;
|
|
|
|
char *buf;
|
|
|
|
|
|
|
|
if (call->enabled)
|
|
|
|
buf = "1\n";
|
|
|
|
else
|
|
|
|
buf = "0\n";
|
|
|
|
|
|
|
|
return simple_read_from_buffer(ubuf, cnt, ppos, buf, 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
event_enable_write(struct file *filp, const char __user *ubuf, size_t cnt,
|
|
|
|
loff_t *ppos)
|
|
|
|
{
|
|
|
|
struct ftrace_event_call *call = filp->private_data;
|
|
|
|
char buf[64];
|
|
|
|
unsigned long val;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (cnt >= sizeof(buf))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (copy_from_user(&buf, ubuf, cnt))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
buf[cnt] = 0;
|
|
|
|
|
|
|
|
ret = strict_strtoul(buf, 10, &val);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
switch (val) {
|
|
|
|
case 0:
|
|
|
|
if (!call->enabled)
|
|
|
|
break;
|
|
|
|
|
|
|
|
call->enabled = 0;
|
|
|
|
call->unregfunc();
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
if (call->enabled)
|
|
|
|
break;
|
|
|
|
|
|
|
|
call->enabled = 1;
|
|
|
|
call->regfunc();
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
*ppos += cnt;
|
|
|
|
|
|
|
|
return cnt;
|
|
|
|
}
|
|
|
|
|
2009-02-24 23:21:36 +08:00
|
|
|
static const struct seq_operations show_event_seq_ops = {
|
|
|
|
.start = t_start,
|
|
|
|
.next = t_next,
|
|
|
|
.show = t_show,
|
|
|
|
.stop = t_stop,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct seq_operations show_set_event_seq_ops = {
|
|
|
|
.start = s_start,
|
|
|
|
.next = s_next,
|
|
|
|
.show = t_show,
|
|
|
|
.stop = t_stop,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct file_operations ftrace_avail_fops = {
|
|
|
|
.open = ftrace_event_seq_open,
|
|
|
|
.read = seq_read,
|
|
|
|
.llseek = seq_lseek,
|
|
|
|
.release = seq_release,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct file_operations ftrace_set_event_fops = {
|
|
|
|
.open = ftrace_event_seq_open,
|
|
|
|
.read = seq_read,
|
|
|
|
.write = ftrace_event_write,
|
|
|
|
.llseek = seq_lseek,
|
|
|
|
.release = seq_release,
|
|
|
|
};
|
|
|
|
|
2009-02-25 03:15:08 +08:00
|
|
|
static const struct file_operations ftrace_enable_fops = {
|
|
|
|
.open = tracing_open_generic,
|
|
|
|
.read = event_enable_read,
|
|
|
|
.write = event_enable_write,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct dentry *event_trace_events_dir(void)
|
|
|
|
{
|
|
|
|
static struct dentry *d_tracer;
|
|
|
|
static struct dentry *d_events;
|
|
|
|
|
|
|
|
if (d_events)
|
|
|
|
return d_events;
|
|
|
|
|
|
|
|
d_tracer = tracing_init_dentry();
|
|
|
|
if (!d_tracer)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
d_events = debugfs_create_dir("events", d_tracer);
|
|
|
|
if (!d_events)
|
|
|
|
pr_warning("Could not create debugfs "
|
|
|
|
"'events' directory\n");
|
|
|
|
|
|
|
|
return d_events;
|
|
|
|
}
|
|
|
|
|
2009-02-28 10:33:02 +08:00
|
|
|
struct event_subsystem {
|
|
|
|
struct list_head list;
|
|
|
|
const char *name;
|
|
|
|
struct dentry *entry;
|
|
|
|
};
|
|
|
|
|
|
|
|
static LIST_HEAD(event_subsystems);
|
|
|
|
|
|
|
|
static struct dentry *
|
|
|
|
event_subsystem_dir(const char *name, struct dentry *d_events)
|
|
|
|
{
|
|
|
|
struct event_subsystem *system;
|
|
|
|
|
|
|
|
/* First see if we did not already create this dir */
|
|
|
|
list_for_each_entry(system, &event_subsystems, list) {
|
|
|
|
if (strcmp(system->name, name) == 0)
|
|
|
|
return system->entry;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* need to create new entry */
|
|
|
|
system = kmalloc(sizeof(*system), GFP_KERNEL);
|
|
|
|
if (!system) {
|
|
|
|
pr_warning("No memory to create event subsystem %s\n",
|
|
|
|
name);
|
|
|
|
return d_events;
|
|
|
|
}
|
|
|
|
|
|
|
|
system->entry = debugfs_create_dir(name, d_events);
|
|
|
|
if (!system->entry) {
|
|
|
|
pr_warning("Could not create event subsystem %s\n",
|
|
|
|
name);
|
|
|
|
kfree(system);
|
|
|
|
return d_events;
|
|
|
|
}
|
|
|
|
|
|
|
|
system->name = name;
|
|
|
|
list_add(&system->list, &event_subsystems);
|
|
|
|
|
|
|
|
return system->entry;
|
|
|
|
}
|
|
|
|
|
2009-02-25 03:15:08 +08:00
|
|
|
static int
|
|
|
|
event_create_dir(struct ftrace_event_call *call, struct dentry *d_events)
|
|
|
|
{
|
|
|
|
struct dentry *entry;
|
|
|
|
|
2009-02-28 10:33:02 +08:00
|
|
|
/*
|
|
|
|
* If the trace point header did not define TRACE_SYSTEM
|
|
|
|
* then the system would be called "TRACE_SYSTEM".
|
|
|
|
*/
|
|
|
|
if (strcmp(call->system, "TRACE_SYSTEM") != 0)
|
|
|
|
d_events = event_subsystem_dir(call->system, d_events);
|
|
|
|
|
2009-02-25 03:15:08 +08:00
|
|
|
call->dir = debugfs_create_dir(call->name, d_events);
|
|
|
|
if (!call->dir) {
|
|
|
|
pr_warning("Could not create debugfs "
|
|
|
|
"'%s' directory\n", call->name);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
entry = debugfs_create_file("enable", 0644, call->dir, call,
|
|
|
|
&ftrace_enable_fops);
|
|
|
|
if (!entry)
|
|
|
|
pr_warning("Could not create debugfs "
|
|
|
|
"'%s/enable' entry\n", call->name);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-02-24 23:21:36 +08:00
|
|
|
static __init int event_trace_init(void)
|
|
|
|
{
|
2009-02-25 03:15:08 +08:00
|
|
|
struct ftrace_event_call *call = __start_ftrace_events;
|
2009-02-24 23:21:36 +08:00
|
|
|
struct dentry *d_tracer;
|
|
|
|
struct dentry *entry;
|
2009-02-25 03:15:08 +08:00
|
|
|
struct dentry *d_events;
|
2009-02-24 23:21:36 +08:00
|
|
|
|
|
|
|
d_tracer = tracing_init_dentry();
|
|
|
|
if (!d_tracer)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
entry = debugfs_create_file("available_events", 0444, d_tracer,
|
|
|
|
(void *)&show_event_seq_ops,
|
|
|
|
&ftrace_avail_fops);
|
|
|
|
if (!entry)
|
|
|
|
pr_warning("Could not create debugfs "
|
|
|
|
"'available_events' entry\n");
|
|
|
|
|
|
|
|
entry = debugfs_create_file("set_event", 0644, d_tracer,
|
|
|
|
(void *)&show_set_event_seq_ops,
|
|
|
|
&ftrace_set_event_fops);
|
|
|
|
if (!entry)
|
|
|
|
pr_warning("Could not create debugfs "
|
|
|
|
"'set_event' entry\n");
|
|
|
|
|
2009-02-25 03:15:08 +08:00
|
|
|
d_events = event_trace_events_dir();
|
|
|
|
if (!d_events)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
events_for_each(call) {
|
|
|
|
/* The linker may leave blanks */
|
|
|
|
if (!call->name)
|
|
|
|
continue;
|
|
|
|
event_create_dir(call, d_events);
|
|
|
|
}
|
|
|
|
|
2009-02-24 23:21:36 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
fs_initcall(event_trace_init);
|