blob: 88fd1c10c2686ccfdf4e2aca576495be6e8a548e [file] [log] [blame]
/** @file
Small impl of an event logging system
@section license License
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@section details Details
This is a small, (hopefully fast) implementation of a event logging
system (per-cost call (including disk i/o) on Sparc was between 25 and
34 us, on Alpha was between 19 and 32 us -- run test_StateEventLogger
with N_THREADS set to "1" to determine this.) It just streams
events to a file. The intended use is for state machine tracing and
debugging and event trace collection, e.g. disk i/o events or object
creation/deletion events.
The usage is:
-# Create a global state logger object for the particular event
type -- there is no requirement for this, just a convention so
that each log file contains events of the same type.
-# When an event occurs in your code, create an event instance and
pass it to the state logger.
-# repeat 2.
Events can be logged with high resolution timestamps and application
specific data. The event instance is marshalled into a machine
independent form (Sparc byte order) before storing to disk.
These functions will be inlined, keep the code path short.
To extend, simply: define a new class derived from StateEvent that
provides marshal() and size() and pass those classes to the state
logger object. Look at TestStateEvent for an example.
Example usage:
@code
StateEventLogger disklog("disk.out");
// ...
disklog(TestStateEvent(my_param1a,my_param2a));
// ...
disklog(TestStateEvent(my_param1b,my_param2b));
@endcode
*/
#ifndef _StateEventLogger_h_
#define _StateEventLogger_h_
// nice property of ring buffer is that buffer update maintenance
// (e.g. writing out data and freeing up buffer space) is constant
// cost, and append is constant. The only cost is that sequential
// acces gets split into possibly two non-contiguous operations.
#include "libts.h"
#include "ink_platform.h"
#include <time.h>
//#define USE_RINGBUF
// This code is too complex for the minimal I/O system call reduction
// that it might provide.
#ifdef USE_RINGBUF
class RingBuf
{
public:
RingBuf(int len); // alloc ring buffer of N bytes
~RingBuf();
int free(); // space available for storing
int avail(); // data available for reading
int flush(int fd, int len); // write data to file
int append(char *buf, int len); // add to buffer
// this is a costly operation. Don't use it. It just was pulled in
// from the library.
char *as_buf(int *len); // returns buffer of length len representing the read.
private:
char *d_buf;
int d_len; // length of buffer
int d_free; // free space
int d_next; // next data
};
#endif
class StateEvent
{ // interface class
public:
virtual ~ StateEvent();
// convert to machine independent form and whack into buffer
#ifdef USE_RINGBUF
virtual void marshal(RingBuf * buf) const = 0;
#else
virtual void marshal(char *buf) const = 0;
#endif
// size of marshalled parameters
virtual int size() const = 0;
};
class TestStateEvent:public StateEvent
{
public:
TestStateEvent()
{
d_param1 = d_param2 = 0;
d_ts = ink_get_hrtime();
}
TestStateEvent(int d1, int d2)
{
d_param1 = d1;
d_param2 = d2;
d_ts = ink_get_hrtime();
}
virtual ~ TestStateEvent() {
};
#ifdef USE_RINGBUF
virtual void marshal(RingBuf * buf) const
{
int x = ntohl(d_param1);
buf->append((char *) &x, sizeof(x));
x = ntohl(d_param2);
buf->append((char *) &x, sizeof(x));
};
#else
virtual void marshal(char *buf) const
{
ink_hrtime t;
t = d_ts;
memcpy(buf, &t, sizeof(t));
buf += sizeof(t);
int x;
x = d_param1;
memcpy(buf, &x, sizeof(x));
buf += sizeof(x);
x = d_param2;
memcpy(buf, &x, sizeof(x));
};
#endif
virtual int size() const
{
return sizeof(d_param1) + sizeof(d_param2) + sizeof(d_ts);
};
// just set these manually or via constructor
ink_hrtime d_ts;
int d_param1;
int d_param2;
};
class StateEventLogger
{
public:
#ifdef USE_RINGBUF
StateEventLogger(char *fname, const StateEvent * s, int nevents = 10000) {
#else
StateEventLogger(char *fname)
{
#endif
printf("opening\n");
fflush(stdout);
d_fd = open(fname, O_RDWR | O_CREAT, 0644);
#ifdef USE_RINGBUF
ink_mutex_init(&d_head_lock, "StateEventLogger:head_lock");
ink_mutex_init(&d_tail_lock, "StateEventLogger:tail_lock");
d_buf = dNEWnew RingBuf(nevents * s->size());
d_highwater = nevents * s->size() / 2;
#endif
};
~StateEventLogger() {
printf("cleaning up\n");
fflush(stdout);
#ifdef USE_RINGBUF
// it would be nice to flush out buffers.
d_buf->flush(d_fd, d_buf->avail());
#endif
close(d_fd);
}
void operator() (const StateEvent & x);
private:
int d_fd; // file to write events to
#ifdef USE_RINGBUF
int d_highwater; // about 1/2 of buffer
RingBuf *d_buf;
ink_mutex d_head_lock;
ink_mutex d_tail_lock;
#endif
};
#endif