Here is a vague start at the directions I have in mind for a user-level
interface that I've been calling "ntrace".  It's not a real specific plan
at the literal interface level.  It's an overview of what the components
are that the bit-level definition of the user-level interface sits atop.

I'll start with a couple of terms that I'll use later throughout the
discussion.

A tracing session is one independent use of the interface, e.g. one
debugger application might have one session to handle many debugees.
Different sessions don't interact or know about each other directly.
A session ends when its users go away, so it always ends automatically
when the debugger applications die suddenly and so forth.  When a
session ends, all its state goes away ceases to mean anything.
Everything I'll talk about later is meant within the context of one
session.  (At some point in the future we might deal with nuances
beyond this, but ignore that for now.)

A tracing "channel" is the term I'll use to encompass the whole subject
of transport.  We'll leave the details of transport aside here, and just
say what the essential characteristics of a channel are.  In the long
run, there can be multiple kinds of channels and there can be multiple
channels in one session.  To start with, we'll only worry about a single
channel per session.

A channel supplies a security context.  (I'll talk separately about the
security model, and we can leave it aside for now.)  A channel can be a
source of commands.  A channel can send data back to the user.

For sending data, a channel might have various buffering options and
characteristics available.  Sending to the channel per se has to be
nonblocking in the kernel.  So e.g., there may be fill-and-drop or
circular buffer options.  For lossless buffering, a channel may need to
ask the sending thread to wait for a buffer drain.  Internally, this may
mean using UTRACE_STOP to hold a thread in check until a channel-drained
callback lets it resume to generate more data.  We don't have to worry
about all that immediately.  But these are some of the subtleties
between the "channel" layer and the "core" of the thing.

I think of the interface as asynchronous at base.  There may at some
point be some synchronous calls to optimize the round trips.  But we
know that by its nature an interface for handling many threads at once
has to be asynchronous, because an event notification can always be
occurring while you are doing an operation on another thread.  So what
keeping it simple means for the first version is that we only worry
about the asynchronous model.  Think of it like a network protocol
between the application and the "tracing server" inside the kernel.
(Don't get hung up on that metaphor if it sounds like a complication to
you.)

One thing we know we need is to send multiple commands to the interface
in a single channel transaction (i.e. system call).  In the general
case, we might have both datagram and stream channels.  But we might as
well only consider the stream case since we have to anyway; i.e., we
can't rely on any intrinsic packetization, we have to recognize
boundaries between separate requests in-band in the command channel.

I think what is the first most important thing about the interface is
not anything much about what's in it.  It's rapid prototyping.  It needs
to be simple on the kernel side to write a new function, with a minimum
of boilerplate, add something to at most two tables, and recompile the
module, to have your new command available in the user-level interface.

As for encoding, I think everything can be composed of "words" and
"blobs".  A word is long/unsigned long in kernel-land.  A blob means a
raw chunk of bytes.  To keep words aligned, I'd encode e.g.
{word=1,blob=A,word=2,blob=B} as:
        word 0: 1
        word 1: <length of blob A>
        word 2: 2
        word 3: <length of blob B>
              : <blob A contents><blob B contents>
              : <padding if lenA+lenB unaligned>
        word n: next unrelated part of the stream

This keeps it straightforward, both on the kernel side and in user-level
interface front ends, to add a new command or report-back that takes n
words and m blobs meaning something or other.

I tend to over-engineer these things.  So I'd been thinking about a
discoverable framework for the command vocabulary.  Down the road I do
want to have some dynamic extensibility.  But we shouldn't get bogged
down in that now.

Job #1 is to get something hobbling along that is easy to hack on, easy
to change, easy to play with, where you pull the cat's tail in user mode
and he's meowing in the kernel module (except there is no cat--ok you
can use a cat if you want to, I said I wasn't addressing transport).

In http://sourceware.org/ml/systemtap/2008-q2/msg00459.html I introduced
"tracing groups" (sorry, it's about half way down that long message).  It
was slightly elaborated in that thread.  I will talk all about groups in
another posting (sorry, will be another bit of delay before I can have that
all written up).  

Some ideas about groups have a lot to do with how I'd like to structure the
command set.  But you don't need to wait to hash that out.  Get started
with whatever commands make sense and are straightforward to use to
demonstrate using the fundaments of the interface.  Don't worry that
everything will get reorganized as we figure it all out (it will, just
don't worry about it).


Thanks,
Roland

Reply via email to