fs_log: merge option, increase message buffer

Maximum amount of in-transit packets is TX_QUEUE_SIZE*2 + 1

Issue #1538
This commit is contained in:
Emery Hemingway 2015-06-09 19:41:48 -04:00 committed by Christian Helmuth
parent ce1e6c16fb
commit 686f53a5c3
4 changed files with 279 additions and 108 deletions

View File

@ -2,11 +2,12 @@ LOG server that writes log messages onto a file system.
Log files are creating in a directory tree formed from session labels. Log files are creating in a directory tree formed from session labels.
As an example the session label "init -> nitpicker" would create As an example the session label "init -> nitpicker" would create
a log file at "init/nitpicker.log". The behavior of opening two LOG a log file at "init/nitpicker.log".
sessions with the same label is undefined.
The only configuration and policy available is the option to truncate The option to truncate files at the start of each LOG session is available
files at the start of the LOG session, which is disabled by default. through session policy, as well the option to merge the logs of any
session matching a given policy. When a merged policy label contains a
trailing "->", the log filename takes the name of the next label element.
:Example configuration: :Example configuration:
! <start name="log_file"> ! <start name="log_file">
@ -14,6 +15,7 @@ files at the start of the LOG session, which is disabled by default.
! <provides><service name="LOG"/></provides> ! <provides><service name="LOG"/></provides>
! <config> ! <config>
! <policy label="nic_drv" truncate="no"/> ! <policy label="nic_drv" truncate="no"/>
! <policy label="cli_monitor -> " merge="yes"/>
! <policy label="" truncate="yes"/> ! <policy label="" truncate="yes"/>
! </config> ! </config>
! </start> ! </start>

View File

@ -0,0 +1,91 @@
/*
* \brief File object shared between log sessions
* \author Emery Hemingway
* \date 2015-06-09
*/
/*
* Copyright (C) 2015 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
#ifndef _FS_LOG__LOG_FILE_H_
#define _FS_LOG__LOG_FILE_H_
/* Genode includes */
#include <log_session/log_session.h>
#include <file_system_session/file_system_session.h>
namespace Fs_log {
using namespace Genode;
using namespace File_system;
class Log_file;
}
class Fs_log::Log_file : public List<Log_file>::Element
{
private:
char _dir_path[ MAX_PATH_LEN];
char _file_name[MAX_NAME_LEN];
File_system::Session &_fs;
File_handle _handle;
seek_off_t _offset;
public:
/**
* Constructor
*/
Log_file(File_system::Session &fs, File_handle handle,
char const *dir_path, char const *file_name,
seek_off_t offset)
:
_fs(fs), _handle(handle), _offset(offset)
{
strncpy(_dir_path, dir_path, sizeof(_dir_path));
strncpy(_file_name, file_name, sizeof(_file_name));
}
bool match(char const *dir, char const *filename) const
{
return
(strcmp(_dir_path, dir, MAX_PATH_LEN) == 0) &&
(strcmp(_file_name, filename, MAX_NAME_LEN) == 0);
}
/**
* Write a log message to the packet buffer.
*/
size_t write(char const *msg, size_t msg_len)
{
File_system::Session::Tx::Source &source = *_fs.tx();
File_system::Packet_descriptor raw_packet;
if (!source.ready_to_submit())
raw_packet = source.get_acked_packet();
else
raw_packet = source.alloc_packet(Log_session::String::MAX_SIZE);
File_system::Packet_descriptor
packet(raw_packet,
0, /* The result struct. */
_handle, File_system::Packet_descriptor::WRITE,
msg_len, _offset);
_offset += msg_len;
char *buf = source.packet_content(packet);
memcpy(buf, msg, msg_len);
source.submit_packet(packet);
return msg_len;
}
};
#endif

View File

@ -20,38 +20,41 @@
#include <base/printf.h> #include <base/printf.h>
/* Local includes */ /* Local includes */
#include "log_file.h"
#include "session.h" #include "session.h"
namespace Fs_log { namespace Fs_log {
using namespace Genode; using namespace Genode;
using namespace File_system;
class Root_component; class Root_component;
struct Main; struct Main;
enum { enum {
BLOCK_SIZE = Log_session::String::MAX_SIZE, BLOCK_SIZE = Log_session::String::MAX_SIZE,
QUEUE_SIZE = File_system::Session::TX_QUEUE_SIZE, QUEUE_SIZE = File_system::Session::TX_QUEUE_SIZE,
TX_BUF_SIZE = BLOCK_SIZE * QUEUE_SIZE TX_BUF_SIZE = BLOCK_SIZE * (QUEUE_SIZE*2 + 1)
}; };
} }
class Fs_log::Root_component : public Genode::Root_component<Fs_log::Session_component> class Fs_log::Root_component :
public Genode::Root_component<Fs_log::Session_component>
{ {
private: private:
Allocator_avl _write_alloc; Allocator_avl _write_alloc;
File_system::Connection _fs; File_system::Connection _fs;
List<Log_file> _log_files;
File_system::File_handle open_file(File_system::Dir_handle &dir_handle, Log_file *lookup(char const *dir, char const *filename)
char const *name)
{ {
try { for (Log_file *file = _log_files.first(); file; file = file->next())
return _fs.file(dir_handle, name, File_system::WRITE_ONLY, false); if (file->match(dir, filename))
} catch (File_system::Lookup_failed) { return file;
return _fs.file(dir_handle, name, File_system::WRITE_ONLY, true);
} return 0;
} }
protected: protected:
@ -60,85 +63,149 @@ class Fs_log::Root_component : public Genode::Root_component<Fs_log::Session_com
{ {
using namespace File_system; using namespace File_system;
char path[MAX_PATH_LEN]; char dir_path[MAX_PATH_LEN];
path[0] = '/'; char file_name[MAX_NAME_LEN];
char name[MAX_NAME_LEN];
Session_label session_label(args); dir_path[0] = '/';
strncpy(path+1, session_label.string(), sizeof(path)-1);
bool truncate = false; bool truncate = false;
Session_label session_label(args);
char const *label_str = session_label.string();
char const *label_prefix = nullptr;
try { try {
Session_policy policy(session_label); Session_policy policy(session_label);
try { try {
truncate = policy.attribute("truncate").has_value("yes"); truncate = policy.attribute("truncate").has_value("yes");
} catch (Xml_node::Nonexistent_attribute) { } } catch (Xml_node::Nonexistent_attribute) { }
} catch (Session_policy::No_policy_defined) { } bool merge = false;
try {
merge = policy.attribute("merge").has_value("yes");
} catch (Xml_node::Nonexistent_attribute) { }
if (merge) {
/*
* Use the policy label that was matched rather than
* full session label for the path of the log file.
*/
policy.attribute("label").value(dir_path+1, sizeof(dir_path)-1);
if (!dir_path[1]) {
PERR("cannot merge an empty policy label");
throw Root::Unavailable();
}
size_t len = strlen(path); /*
size_t start = 1; * If the policy has a trailing '->', move first element
for (size_t i = 1; i < len;) { * from the log prefix to the end of the log path.
/* Replace any slashes in label elements. */ */
if (path[i] == '/') path[i] = '_'; size_t label_len = strlen(dir_path);
if (strcmp(" -> ", path+i, 4) == 0) { label_prefix = label_str+(label_len-1);
path[i++] = '/';
strncpy(path+i, path+i+3, sizeof(path)-i); if ((strcmp((dir_path+label_len)-3, " ->", 4) == 0) ||
start = i; (strcmp((dir_path+label_len)-4, " -> ", 5) == 0)) {
i += 3;
} else ++i; for (size_t i = 0; *(label_str+i); ++i) {
if (strcmp(label_prefix+i, " -> ", 4))
continue;
strncpy(dir_path+label_len, label_prefix, i+1);
label_prefix += i+4;
break;
}
if (*label_prefix == ' ') ++label_prefix;
}
} else
strncpy(dir_path+1, label_str, MAX_PATH_LEN-1);
} catch (Session_policy::No_policy_defined) {
strncpy(dir_path+1, label_str, MAX_PATH_LEN-1);
} }
snprintf(name, sizeof(name), "%s.log", path+start); {
path[(start == 1) ? start : start-1] = '\0'; /* Parse out a directory and file name. */
size_t len = strlen(dir_path);
size_t start = 1;
for (size_t i = 1; i < len;) {
/* Replace any slashes in label elements. */
if (dir_path[i] == '/') dir_path[i] = '_';
if (strcmp(" -> ", dir_path+i, 4) == 0) {
dir_path[i++] = '/';
strncpy(dir_path+i, dir_path+i+3, MAX_PATH_LEN-i);
start = i;
i += 3;
} else ++i;
}
/* Rewrite any slashes in the name. */ /* Copy the remainder to the file name. */
for (char *p = name; *p; ++p) snprintf(file_name, MAX_NAME_LEN, "%s.log", dir_path+start);
if (*p == '/') *p = '_';
File_handle file_handle; /* Terminate the directory path. */
seek_off_t offset = 0; dir_path[(start == 1) ? start : start-1] = '\0';
try {
Dir_handle dir_handle = ensure_dir(_fs, path); /* Rewrite any slashes in the name. */
for (char *p = file_name; *p; ++p)
if (*p == '/') *p = '_';
}
Log_file *file = lookup(dir_path, file_name);
if (!file) try {
Dir_handle dir_handle = ensure_dir(_fs, dir_path);
Handle_guard dir_guard(_fs, dir_handle); Handle_guard dir_guard(_fs, dir_handle);
File_handle handle;
seek_off_t offset = 0;
file_handle = open_file(dir_handle, name); try {
handle = _fs.file(dir_handle, file_name,
File_system::WRITE_ONLY, false);
if (truncate) if (truncate)
_fs.truncate(file_handle, 0); _fs.truncate(handle, 0);
else else
offset = _fs.status(file_handle).size; offset = _fs.status(handle).size;
} catch (File_system::Lookup_failed) {
handle = _fs.file(dir_handle, file_name,
File_system::WRITE_ONLY, true);
}
file = new (env()->heap())
Log_file(_fs, handle, dir_path, file_name, offset);
_log_files.insert(file);
} catch (Permission_denied) { } catch (Permission_denied) {
PERR("%s/%s: permission denied", path, name); PERR("%s:%s: permission denied", dir_path, file_name);
throw Root::Unavailable();
} catch (Name_too_long) { } catch (Name_too_long) {
PERR("%s/%s: name too long", path, name); PERR("%s:%s: name too long", dir_path, file_name);
throw Root::Unavailable();
} catch (No_space) { } catch (No_space) {
PERR("%s/%s: no space", path, name); PERR("%s:%s: no space", dir_path, file_name);
throw Root::Unavailable();
} catch (Out_of_node_handles) { } catch (Out_of_node_handles) {
PERR("%s/%s: out of node handles", path, name); PERR("%s:%s: out of node handles", dir_path, file_name);
throw Root::Unavailable();
} catch (Invalid_name) { } catch (Invalid_name) {
PERR("%s/%s: invalid_name", path, name); PERR("%s:%s: invalid_name", dir_path, file_name);
throw Root::Unavailable();
} catch (Size_limit_reached) { } catch (Size_limit_reached) {
PERR("%s/%s: size limit reached", path, name); PERR("%s:%s: size limit reached", dir_path, file_name);
throw Root::Unavailable();
} catch (...) { } catch (...) {
PERR("%s/%s: unknown error", path, name); PERR("%s:%s: unknown error", dir_path, file_name);
throw;
}
if (!file) {
PERR("file was null");
throw Root::Unavailable(); throw Root::Unavailable();
} }
return new (md_alloc()) Session_component(_fs, file_handle, offset); if (label_prefix && *label_prefix)
return new (md_alloc()) Labeled_session_component(label_prefix, *file);
return new (md_alloc()) Unlabeled_session_component(*file);
} }
public: public:

View File

@ -2,6 +2,9 @@
* \brief Log session that writes messages to a file system. * \brief Log session that writes messages to a file system.
* \author Emery Hemingway * \author Emery Hemingway
* \date 2015-05-16 * \date 2015-05-16
*
* Message writing is fire-and-forget to prevent
* logging from becoming I/O bound.
*/ */
/* /*
@ -19,81 +22,89 @@
#include <file_system_session/file_system_session.h> #include <file_system_session/file_system_session.h>
#include <base/rpc_server.h> #include <base/rpc_server.h>
/* Local includes */
#include "log_file.h"
namespace Fs_log { namespace Fs_log {
class Session_component;
using namespace Genode; class Unlabeled_session_component;
class Labeled_session_component;
class Session_component;
} }
/** class Fs_log::Session_component : public Rpc_object<Log_session, Unlabeled_session_component>
* A log session that writes messages to a file system node. {
* public:
* Message writing is fire-and-forget to prevent virtual size_t write(String const &string) = 0;
* logging from becoming I/O bound. };
*/
class Fs_log::Session_component : public Rpc_object<Log_session, Session_component> class Fs_log::Unlabeled_session_component : public Session_component
{ {
private: private:
File_system::Session &_fs; Log_file &_log_file;
File_system::File_handle _file_handle;
File_system::seek_off_t _offset;
public: public:
/** /**
* Constructor * Constructor
*/ */
Session_component(File_system::Session &fs, Unlabeled_session_component(Log_file &log_file)
File_system::File_handle fh, : _log_file(log_file) { }
File_system::seek_off_t offset)
: _fs(fs), _file_handle(fh), _offset(offset) { }
/***************** /*****************
** Log session ** ** Log session **
*****************/ *****************/
size_t write(Log_session::String const &string) size_t write(Log_session::String const &msg)
{ {
if (!(string.is_valid_string())) { if (!msg.is_valid_string()) {
PERR("corrupted string"); PERR("corrupted string");
return 0; return 0;
} }
File_system::Session::Tx::Source &source = *_fs.tx(); char const *msg_str = msg.string();
size_t msg_len = Genode::strlen(msg_str);
char const *msg = string.string(); return _log_file.write(msg_str, msg_len);
size_t msg_len = Genode::strlen(msg); }
size_t write_len = msg_len; };
/* class Fs_log::Labeled_session_component : public Session_component
* If the message did not fill the incoming buffer {
* make space to add a newline. private:
*/
if ((msg_len < Log_session::String::MAX_SIZE) &&
(msg[msg_len-1] != '\n'))
++write_len;
while (source.ack_avail()) char _label[Log_session::String::MAX_SIZE];
source.release_packet(source.get_acked_packet()); size_t _label_len;
Log_file &_log_file;
File_system::Packet_descriptor public:
packet(source.alloc_packet(Log_session::String::MAX_SIZE),
0, /* The result struct. */
_file_handle, File_system::Packet_descriptor::WRITE,
write_len, _offset);
_offset += write_len;
char *buf = source.packet_content(packet); /**
memcpy(buf, msg, msg_len); * Constructor
*/
Labeled_session_component(char const *label, Log_file &log_file)
: _log_file(log_file)
{
snprintf(_label, sizeof(_label), "[%s] ", label);
_label_len = strlen(_label);
}
if (msg_len != write_len) /*****************
buf[msg_len] = '\n'; ** Log session **
*****************/
source.submit_packet(packet); size_t write(Log_session::String const &msg)
return msg_len; {
if (!msg.is_valid_string()) {
PERR("corrupted string");
return 0;
}
char const *msg_str = msg.string();
size_t msg_len = Genode::strlen(msg_str);
_log_file.write(_label, _label_len);
return _log_file.write(msg_str, msg_len);
} }
}; };