vfs_trace: VFS plugin that offers trace buffer access

The plugin creates a file-system hierarchy that enabled the access of
trace buffers for each component and its threads.

issue #3294
This commit is contained in:
Sebastian Sumpf 2019-04-11 11:20:48 +02:00 committed by Christian Helmuth
parent 25484f870e
commit 18b3253cac
8 changed files with 1040 additions and 0 deletions

View File

@ -0,0 +1,5 @@
SRC_CC = vfs.cc
vpath %.cc $(REP_DIR)/src/lib/vfs/trace
SHARED_LIB = yes

View File

@ -0,0 +1,34 @@
The VFS trace plugin offers access to Genode's trace session by providing a
file-system that can be mounted at arbitrary location within the VFS of a
component. The file system forms a directory structure that recursively
represents the parent-child relationship of running component. The leave
directories represent the threads within a component. Currently there are three
files for each thread that can be accessed:
:'enable': Start or stops tracing of a thread by write "true" or "false" into
file.
:'buffer_size': Allows the configuration of the trace-buffer size for the
thread in the usual Genode format (e.g. 5M, 512K, 1024).
:'trace_buffer': This read only file contains the current content of the trace
buffer. Every trace entry can only be read once, after that
only new entries appear. "tail -f" can also be used in order to
display continuous output.
In order to mount the file system configure the <vfs> of your component as
follow:
! <vfs>
! <trace ram="16M"/>
! </vfs>
The "ram" attribute is mandatory and configures the quota of the trace-session
in the plugin. It limits the number of thread that can be traced at once.
Limitations:
The plugin retrieves up to 128 running components at load time. There is
currently no support to add or remove components once the plugin has been
started. This implies that components that are started at a later stage will not
appear within the file system.

View File

@ -0,0 +1,150 @@
/*
* \brief A tree of AVL trees that forms a directory structure
* \author Sebastian Sumpf
* \date 2019-06-14
*/
/*
* Copyright (C) 2019 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _DIRECTORY_TREE_H_
#define _DIRECTORY_TREE_H_
#include <util/avl_string.h>
#include "session_label.h"
namespace Vfs {
class Directory_tree;
class Trace_node;
struct Label;
}
namespace Genode {
template <typename> class Avl_node_tree;
}
template <typename NT>
class Genode::Avl_node_tree : public NT
{
protected:
using Tree = Avl_tree<NT>;
using Node = Avl_node<NT>;
Tree _tree { };
public:
using NT::NT;
void insert(Node *node) { _tree.insert(node); }
Tree &tree() { return _tree; }
Node *find_by_name(char const *name)
{
if (!_tree.first()) return nullptr;
Node *node = _tree.first()->find_by_name(name);
return node;
}
};
struct Vfs::Label : Genode::String<32>
{
using String::String;
};
class Vfs::Trace_node : public Vfs::Label,
public Avl_node_tree<Genode::Avl_string_base>
{
private:
Allocator &_alloc;
Trace::Subject_id const _id;
Trace_node *_find_by_name(char const *name)
{
Node *node = find_by_name(name);
return node ? static_cast<Trace_node *>(node) : nullptr;
}
public:
Trace_node(Genode::Allocator &alloc, Session_label const &label,
Trace::Subject_id const id = 0)
: Vfs::Label(label), Avl_node_tree(string()),
_alloc(alloc), _id(id)
{ }
Trace_node &insert(Session_label const &label)
{
if (!label.valid()) return *this;
Trace_node *node = _find_by_name(label.first_element().string());
if (!node) {
node = new(_alloc) Trace_node(_alloc, label.first_element());
Avl_node_tree::insert(node);
}
return node->insert(label.suffix());
}
void xml(Genode::Xml_generator &xml) const
{
_tree.for_each([&] (Genode::Avl_string_base const &name) {
Trace_node const &node = static_cast<Trace_node const &>(name);
if (node.id() == 0)
xml.node("dir", [&] () {
xml.attribute("name", node.name());
node.xml(xml);
});
else
xml.node("trace_node", [&] () {
xml.attribute("name", node.name());
xml.attribute("id", node.id().id);
node.xml(xml);
});
});
}
Trace::Subject_id const &id() const { return _id; }
};
class Vfs::Directory_tree : public Genode::Avl_tree<Trace_node>
{
private:
Allocator &_alloc;
Trace_node _root { _alloc, Session_label() };
public:
Directory_tree(Genode::Allocator &alloc)
: _alloc(alloc) { }
void insert(Trace::Subject_info const &info, Trace::Subject_id const id)
{
Trace_node &leaf = _root.insert(info.session_label());
Trace_node *node = new (_alloc) Trace_node(_alloc, info.thread_name(), id);
leaf.Avl_node_tree::insert(node);
}
void xml(Genode::Xml_generator &xml)
{
_root.xml(xml);
}
};
#endif /* _DIRECTORY_TREE_H_ */

View File

@ -0,0 +1,69 @@
/*
* \brief Session label extension
* \author Sebastian Sumpf
* \date 2019-06-04
*/
/*
* Copyright (C) 2019 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _SESSION_LABEL_H_
#define _SESSION_LABEL_H_
#include <base/snprintf.h>
#include <util/arg_string.h>
#include <util/string.h>
namespace Vfs {
using namespace Genode;
struct Session_label;
}
struct Vfs::Session_label : Genode::Session_label
{
private:
static char const *_separator() { return " -> "; }
static size_t _separator_len() { return 4; }
public:
using Genode::Session_label::Session_label;
Session_label first_element() const
{
char const * const full = string();
if (length() < _separator_len() + 1)
return Session_label(Cstring(full));
unsigned prefix_len;
for (prefix_len = 0; prefix_len < length() - 1; prefix_len++)
if (!strcmp(_separator(), full + prefix_len, _separator_len()))
break;
return Session_label(Cstring(full, prefix_len));
}
/**
* Return part of the label without first element
*/
Session_label suffix() const
{
if (length() < _separator_len() + 1)
return Session_label();
char const * const full = string();
for (unsigned prefix_len = 0; prefix_len < length() - 1; prefix_len++)
if (!strcmp(_separator(), full + prefix_len, _separator_len()))
return full + prefix_len + _separator_len();
return Session_label();
}
};
#endif /* _SESSION_LABEL_H_ */

View File

@ -0,0 +1 @@
LIBS = vfs_trace

View File

@ -0,0 +1,79 @@
/*
* \brief Wrapper for Trace::Buffer that adds some convenient functionality
* \author Martin Stein
* \date 2018-01-12
*/
/*
* Copyright (C) 2018 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _TRACE_BUFFER_H_
#define _TRACE_BUFFER_H_
/* Genode includes */
#include <base/trace/buffer.h>
/**
* Wrapper for Trace::Buffer that adds some convenient functionality
*/
class Trace_buffer
{
private:
Genode::Trace::Buffer &_buffer;
Genode::Trace::Buffer::Entry _curr { _buffer.first() };
unsigned _wrapped_count { 0 };
public:
Trace_buffer(Genode::Trace::Buffer &buffer) : _buffer(buffer) { }
/**
* Call functor for each entry that wasn't yet processed
*/
template <typename FUNC>
void for_each_new_entry(FUNC && functor, bool update = true)
{
using namespace Genode;
bool wrapped = _buffer.wrapped() != _wrapped_count;
if (wrapped)
_wrapped_count = _buffer.wrapped();
/* initialize _curr if _buffer was empty until now */
Trace::Buffer::Entry curr { _curr };
if (_curr.last())
curr = _buffer.first();
/* iterate over all entries that were not processed yet */
Trace::Buffer::Entry e1 = curr;
for (Trace::Buffer::Entry e2 = curr; wrapped || !e2.last();
e2 = _buffer.next(e2)) {
/* if buffer wrapped, we pass the last entry once and continue at first entry */
if (wrapped && e2.last()) {
wrapped = false;
e2 = _buffer.first();
if (e2.last())
break;
}
e1 = e2;
if (!functor(e1))
break;
}
/* remember the last processed entry in _curr */
curr = e1;
if (update) _curr = curr;
}
void * address() const { return &_buffer; }
};
#endif /* _TRACE_BUFFER_H_ */

View File

@ -0,0 +1,228 @@
/*
* \brief File system for providing a value as a file
* \author Josef Soentgen
* \author Sebastian Sumpf
* \date 2018-11-24
*/
/*
* Copyright (C) 2018-2019 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _VALUE_FILE_SYSTEM_H_
#define _VALUE_FILE_SYSTEM_H_
/* Genode includes */
#include <util/xml_generator.h>
#include <vfs/single_file_system.h>
namespace Vfs {
template <typename, unsigned BUF_SIZE = 64>
class Value_file_system;
}
template <typename T, unsigned BUF_SIZE>
class Vfs::Value_file_system : public Vfs::Single_file_system
{
public:
typedef Genode::String<64> Name;
private:
typedef Genode::String<BUF_SIZE + 1> Buffer;
Name const _file_name;
Buffer _buffer { };
struct Vfs_handle : Single_vfs_handle
{
Value_file_system &_value_fs;
Buffer &_buffer{ _value_fs._buffer };
Vfs_handle(Value_file_system &value_fs,
Allocator &alloc)
:
Single_vfs_handle(value_fs, value_fs, alloc, 0),
_value_fs(value_fs)
{ }
Read_result read(char *dst, file_size count,
file_size &out_count) override
{
out_count = 0;
if (seek() > _buffer.length())
return READ_ERR_INVALID;
char const * const src = _buffer.string() + seek();
Genode::size_t const len = min(_buffer.length() - seek(), count);
Genode::memcpy(dst, src, len);
out_count = len;
return READ_OK;
}
Write_result write(char const *src, file_size count, file_size &out_count) override
{
out_count = 0;
if (seek() > BUF_SIZE)
return WRITE_ERR_INVALID;
Genode::size_t const len = min(BUF_SIZE- seek(), count);
_buffer = Buffer(Cstring(src, len));
out_count = len;
/* inform watchers */
_value_fs._watch_response();
return WRITE_OK;
}
bool read_ready() override { return true; }
private:
Vfs_handle(Vfs_handle const &);
Vfs_handle &operator = (Vfs_handle const &);
};
struct Watch_handle;
using Watch_handle_registry = Genode::Registry<Watch_handle>;
struct Watch_handle : Vfs_watch_handle
{
typename Watch_handle_registry::Element elem;
Watch_handle(Watch_handle_registry &registry,
Vfs::File_system &fs,
Allocator &alloc)
: Vfs_watch_handle(fs, alloc), elem(registry, *this) { }
};
Watch_handle_registry _watch_handle_registry { };
void _watch_response() {
_watch_handle_registry.for_each([&] (Watch_handle &h) {
h.watch_response();
});
}
typedef Genode::String<200> Config;
Config _config(Name const &name) const
{
char buf[Config::capacity()] { };
Genode::Xml_generator xml(buf, sizeof(buf), type_name(), [&] () {
xml.attribute("name", name); });
return Config(Genode::Cstring(buf));
}
public:
Value_file_system(Name const &name, Buffer const &initial_value)
:
Single_file_system(NODE_TYPE_CHAR_DEVICE, type(),
Xml_node(_config(name).string())),
_file_name(name)
{
value(initial_value);
}
static char const *type_name() { return "value"; }
char const *type() override { return type_name(); }
void value(Buffer const &value)
{
_buffer = Buffer(value);
}
T value()
{
T val { 0 };
Genode::ascii_to(_buffer.string(), val);
return val;
}
Buffer buffer() const { return _buffer; }
bool matches(Xml_node node) const
{
return node.has_type(type_name()) &&
node.attribute_value("name", Name()) == _file_name;
}
/********************************
** File I/O service interface **
********************************/
Ftruncate_result ftruncate(Vfs::Vfs_handle *, file_size size) override
{
if (size >= BUF_SIZE)
return FTRUNCATE_ERR_NO_SPACE;
return FTRUNCATE_OK;
}
/*********************************
** Directory-service interface **
*********************************/
Open_result open(char const *path, unsigned,
Vfs::Vfs_handle **out_handle,
Allocator &alloc) override
{
if (!_single_file(path))
return OPEN_ERR_UNACCESSIBLE;
try {
*out_handle = new (alloc) Vfs_handle(*this, alloc);
return OPEN_OK;
}
catch (Genode::Out_of_ram) { Genode::error("out of ram"); return OPEN_ERR_OUT_OF_RAM; }
catch (Genode::Out_of_caps) { Genode::error("out of caps");return OPEN_ERR_OUT_OF_CAPS; }
}
Stat_result stat(char const *path, Stat &out) override
{
Stat_result result = Single_file_system::stat(path, out);
out.mode |= 0666;
out.size = BUF_SIZE + 1;
return result;
}
Watch_result watch(char const *path,
Vfs_watch_handle **handle,
Allocator &alloc) override
{
if (!_single_file(path))
return WATCH_ERR_UNACCESSIBLE;
try {
Watch_handle *wh = new (alloc)
Watch_handle(_watch_handle_registry, *this, alloc);
*handle = wh;
return WATCH_OK;
}
catch (Genode::Out_of_ram) { return WATCH_ERR_OUT_OF_RAM; }
catch (Genode::Out_of_caps) { return WATCH_ERR_OUT_OF_CAPS; }
}
void close(Vfs_watch_handle *handle) override
{
if (handle && (&handle->fs() == this))
destroy(handle->alloc(), handle);
}
};
#endif /* _VALUE_FILE_SYSTEM_H_ */

View File

@ -0,0 +1,474 @@
/*
* \brief File system for trace buffer access
* \author Sebastian Sumpf
* \date 2019-06-13
*/
/*
* Copyright (C) 2019 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#include <base/attached_rom_dataspace.h>
#include <vfs/dir_file_system.h>
#include <vfs/single_file_system.h>
#include <gems/vfs.h>
#include <util/xml_generator.h>
#include <trace_session/connection.h>
#include "directory_tree.h"
#include "trace_buffer.h"
#include "value_file_system.h"
namespace Vfs_trace {
using namespace Vfs;
using namespace Genode;
using Name = String<32>;
struct File_system;
class Local_factory;
class Subject;
struct Subject_factory;
class Trace_buffer_file_system;
}
class Vfs_trace::Trace_buffer_file_system : public Single_file_system
{
private:
/**
* Trace_buffer wrapper
*/
struct Trace_entries
{
Vfs::Env &_env;
Constructible<Trace_buffer> _buffer { };
Trace_entries(Vfs::Env &env) : _env(env) { }
void setup(Dataspace_capability ds)
{
_buffer.construct(*((Trace::Buffer *)_env.env().rm().attach(ds)));
}
void flush()
{
if (!_buffer.constructed()) return;
_env.env().rm().detach(_buffer->address());
_buffer.destruct();
}
template <typename FUNC>
void for_each_new_entry(FUNC && functor, bool update = true) {
if (!_buffer.constructed()) return;
_buffer->for_each_new_entry(functor, update);
}
};
enum State { OFF, TRACE, PAUSED } _state { OFF };
Vfs::Env &_env;
Trace::Connection &_trace;
Trace::Policy_id _policy;
Trace::Subject_id _id;
size_t _buffer_size { 1024 * 1024 };
size_t _stat_size { 0 };
Trace_entries _entries { _env };
typedef String<32> Config;
static Config _config()
{
char buf[Config::capacity()] { };
Xml_generator xml(buf, sizeof(buf), type_name(), [&] () { });
return Config(Cstring(buf));
}
void _setup_and_trace()
{
_entries.flush();
try {
_trace.trace(_id, _policy, _buffer_size);
} catch (...) {
error("failed to start tracing");
return;
}
_entries.setup(_trace.buffer(_id));
}
public:
struct Vfs_handle : Single_vfs_handle
{
Trace_entries &_entries;
Vfs_handle(Directory_service &ds,
File_io_service &fs,
Genode::Allocator &alloc,
Trace_entries &entries)
: Single_vfs_handle(ds, fs, alloc, 0), _entries(entries)
{ }
Read_result read(char *dst, file_size count,
file_size &out_count) override
{
out_count = 0;
_entries.for_each_new_entry([&](Trace::Buffer::Entry entry) {
file_size size = min(count - out_count, entry.length());
memcpy(dst + out_count, entry.data(), size);
out_count += size;
if (out_count == count)
return false;
return true;
});
return READ_OK;
}
Write_result write(char const *, file_size,
file_size &out_count) override
{
out_count = 0;
return WRITE_ERR_INVALID;
}
bool read_ready() override { return true; }
};
Trace_buffer_file_system(Vfs::Env &env,
Trace::Connection &trace,
Trace::Policy_id policy,
Trace::Subject_id id)
: Single_file_system(NODE_TYPE_CHAR_DEVICE,
type_name(), Xml_node(_config().string())),
_env(env), _trace(trace), _policy(policy), _id(id)
{ }
static char const *type_name() { return "trace_buffer"; }
char const *type() override { return type_name(); }
/***************************
** File-system interface **
***************************/
Open_result open(char const *path, unsigned,
Vfs::Vfs_handle **out_handle,
Allocator &alloc) override
{
if (!_single_file(path))
return OPEN_ERR_UNACCESSIBLE;
*out_handle = new (alloc) Vfs_handle(*this, *this, alloc, _entries);
return OPEN_OK;
}
Stat_result stat(char const *path, Stat &out) override
{
Stat_result res = Single_file_system::stat(path, out);
if (res != STAT_OK) return res;
/* update file size */
if (_state == TRACE)
_entries.for_each_new_entry([&](Trace::Buffer::Entry entry) {
_stat_size += entry.length(); return true; }, false);
out.size = _stat_size;
return res;
}
/***********************
** FS event handlers **
***********************/
bool resize_buffer(size_t size)
{
if (size == 0) return false;
_buffer_size = size;
switch (_state) {
case TRACE:
_trace.pause(_id);
_setup_and_trace();
break;
case PAUSED:
_state = OFF;
break;
case OFF:
break;
}
return true;
}
void trace(bool enable)
{
if (enable) {
switch (_state) {
case TRACE: break;
case OFF: _setup_and_trace(); break;
case PAUSED: _trace.resume(_id); break;
}
_state = TRACE;
} else {
switch (_state) {
case OFF: return;
case PAUSED: return;
case TRACE: _trace.pause(_id); _state = PAUSED; return;
}
}
}
};
struct Vfs_trace::Subject_factory : File_system_factory
{
Vfs::Env &_env;
Value_file_system<bool, 6> _enabled_fs
{ "enable", "false\n"};
Value_file_system<Number_of_bytes, 16> _buffer_size_fs
{ "buffer_size", "1M\n"};
String<17> _buffer_string
{ _buffer_size_fs.buffer() };
Trace_buffer_file_system _trace_fs;
Subject_factory(Vfs::Env &env,
Trace::Connection &trace,
Trace::Policy_id policy,
Trace::Subject_id id)
: _env(env), _trace_fs(env, trace, policy, id) { }
Vfs::File_system *create(Vfs::Env &, Xml_node node) override
{
if (node.has_type(Value_file_system<unsigned>::type_name())) {
if (_enabled_fs.matches(node)) return &_enabled_fs;
if (_buffer_size_fs.matches(node)) return &_buffer_size_fs;
}
if (node.has_type(Trace_buffer_file_system::type_name()))
return &_trace_fs;
return nullptr;
}
};
class Vfs_trace::Subject : private Subject_factory,
public Vfs::Dir_file_system
{
private:
typedef String<200> Config;
Watch_handler<Subject> _enable_handler {
_enabled_fs, "/enable",
Subject_factory::_env.alloc(),
*this, &Subject::_enable_subject };
Watch_handler<Subject> _buffer_size_handler {
_buffer_size_fs, "/buffer_size",
Subject_factory::_env.alloc(),
*this, &Subject::_buffer_size };
static Config _config(Xml_node node)
{
char buf[Config::capacity()] { };
Xml_generator xml(buf, sizeof(buf), "dir", [&] () {
xml.attribute("name", node.attribute_value("name", Vfs_trace::Name()));
xml.node("value", [&] () { xml.attribute("name", "enable"); });
xml.node("value", [&] () { xml.attribute("name", "buffer_size"); });
xml.node(Trace_buffer_file_system::type_name(), [&] () {});
});
return Config(Cstring(buf));
}
/********************
** Watch handlers **
********************/
void _enable_subject()
{
_enabled_fs.value(_enabled_fs.value() ? "true\n" : "false\n");
_trace_fs.trace(_enabled_fs.value());
}
void _buffer_size()
{
Number_of_bytes size = _buffer_size_fs.value();
if (_trace_fs.resize_buffer(size) == false) {
/* restore old value */
_buffer_size_fs.value(_buffer_string);
return;
}
_buffer_string = _buffer_size_fs.buffer();
}
public:
Subject(Vfs::Env &env, Trace::Connection &trace,
Trace::Policy_id policy, Xml_node node)
: Subject_factory(env, trace, policy, node.attribute_value("id", 0u)),
Dir_file_system(env, Xml_node(_config(node).string()), *this)
{ }
static char const *type_name() { return "trace_node"; }
char const *type() override { return type_name(); }
};
struct Vfs_trace::Local_factory : File_system_factory
{
Vfs::Env &_env;
Trace::Connection _trace;
enum { MAX_SUBJECTS = 128 };
Trace::Subject_id _subjects[MAX_SUBJECTS];
unsigned _subject_count { 0 };
Trace::Policy_id _policy_id { 0 };
Directory_tree _tree { _env.alloc() };
void _install_null_policy()
{
using namespace Genode;
Constructible<Attached_rom_dataspace> null_policy;
try {
null_policy.construct(_env.env(), "null");
_policy_id = _trace.alloc_policy(null_policy->size());
}
catch (Out_of_caps) { throw; }
catch (Out_of_ram) { throw; }
catch (...) {
error("failed to attach 'null' trace policy."
"Please make sure it is provided as a ROM module.");
throw;
}
/* copy policy into trace session */
void *dst = _env.env().rm().attach(_trace.policy(_policy_id));
memcpy(dst, null_policy->local_addr<void*>(), null_policy->size());
_env.env().rm().detach(dst);
}
size_t _config_session_ram(Xml_node config)
{
try {
Genode::Number_of_bytes ram;
config.attribute("ram").value(&ram);
return ram;
} catch (...) {
Genode::error("mandatory 'ram' attribute missing");
throw Genode::Exception();
}
}
Local_factory(Vfs::Env &env, Xml_node config)
: _env(env), _trace(env.env(), _config_session_ram(config), 512*1024, 0)
{
bool success = false;
while (!success) {
try {
_subject_count = _trace.subjects(_subjects, MAX_SUBJECTS);
success = true;
} catch(Genode::Out_of_ram) {
_trace.upgrade_ram(4096);
success = false;
}
}
for (unsigned i = 0; i < _subject_count; i++) {
_tree.insert(_trace.subject_info(_subjects[i]), _subjects[i]);
}
_install_null_policy();
}
Vfs::File_system *create(Vfs::Env&, Xml_node node) override
{
if (node.has_type(Subject::type_name()))
return new (_env.alloc()) Subject(_env, _trace, _policy_id, node);
return nullptr;
}
};
class Vfs_trace::File_system : private Local_factory,
public Vfs::Dir_file_system
{
private:
typedef String<512*1024> Config;
static char const *_config(Vfs::Env &vfs_env, Directory_tree &tree)
{
char *buf = (char *)vfs_env.alloc().alloc(Config::capacity());
Xml_generator xml(buf, Config::capacity(), "node", [&] () {
tree.xml(xml);
});
return buf;
}
public:
File_system(Vfs::Env &vfs_env, Genode::Xml_node node)
: Local_factory(vfs_env, node),
Vfs::Dir_file_system(vfs_env, Xml_node(_config(vfs_env, _tree)), *this)
{ }
char const *type() override { return "trace"; }
};
/**************************
** VFS plugin interface **
**************************/
extern "C" Vfs::File_system_factory *vfs_file_system_factory(void)
{
struct Factory : Vfs::File_system_factory
{
Vfs::File_system *create(Vfs::Env &vfs_env,
Genode::Xml_node node) override
{
try { return new (vfs_env.alloc())
Vfs_trace::File_system(vfs_env, node); }
catch (...) { Genode::error("could not create 'trace_fs' "); }
return nullptr;
}
};
static Factory factory;
return &factory;
}