mirror of
git://git.sv.gnu.org/emacs.git
synced 2025-12-29 08:31:35 -08:00
Merge nb's eventcnv changes and rewrite guide/debug and topic/telemetry accordingly.
Copied from Perforce Change: 180103 ServerID: perforce.ravenbrook.com
This commit is contained in:
commit
cec0b453ee
9 changed files with 1244 additions and 357 deletions
|
|
@ -120,7 +120,7 @@ __extension__ typedef unsigned long long EventClock;
|
|||
#define EVENT_CLOCK_PRINT(stream, clock) \
|
||||
fprintf(stream, "%08lX%08lX", \
|
||||
(unsigned long)((clock) >> 32), \
|
||||
(unsigned long)(clock))
|
||||
(unsigned long)((clock) & 0xffffffff))
|
||||
|
||||
#define EVENT_CLOCK_WRITE(stream, clock) \
|
||||
WriteF(stream, "$W$W", (WriteFW)((clock) >> 32), (WriteFW)clock, NULL)
|
||||
|
|
|
|||
|
|
@ -178,6 +178,8 @@ void EventInit(void)
|
|||
EventKindControl = (Word)mps_lib_telemetry_control();
|
||||
EventInternSerial = (Serial)1; /* 0 is reserved */
|
||||
(void)EventInternString(MPSVersion()); /* emit version */
|
||||
EVENT6(EventInit, EVENT_VERSION_MAJOR, EVENT_VERSION_MEDIAN,
|
||||
EVENT_VERSION_MINOR, EventCodeMAX, EventNameMAX, MPS_WORD_WIDTH);
|
||||
} else {
|
||||
++eventUserCount;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Copyright (c) 2001 Ravenbrook Limited. See end of file for license.
|
||||
*
|
||||
* This is a command-line tool that converts a binary format telemetry output
|
||||
* stream from the MPS into several textual formats.
|
||||
* stream from the MPS into a textual format.
|
||||
*
|
||||
* The default MPS library will write a telemetry stream to a file called
|
||||
* "mpsio.log" when the environment variable MPS_TELEMETRY_CONTROL is set
|
||||
|
|
@ -22,20 +22,16 @@
|
|||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "eventdef.h"
|
||||
#include "eventcom.h"
|
||||
#include "eventpro.h"
|
||||
#include "mpmtypes.h"
|
||||
#include "testlib.h" /* for ulongest_t and associated print formats */
|
||||
|
||||
#include <stddef.h> /* for size_t */
|
||||
#include <stdio.h> /* for printf */
|
||||
#include <stdarg.h> /* for va_list */
|
||||
#include <stdlib.h> /* for EXIT_FAILURE */
|
||||
#include <assert.h> /* for assert */
|
||||
#include <string.h> /* for strcmp */
|
||||
#include <math.h> /* for sqrt */
|
||||
#include "mpstd.h"
|
||||
|
||||
#ifdef MPS_BUILD_MV
|
||||
|
|
@ -44,20 +40,12 @@
|
|||
#pragma warning( disable : 4996 )
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
typedef unsigned int uint;
|
||||
typedef unsigned long ulong;
|
||||
|
||||
#define DEFAULT_TELEMETRY_FILENAME "mpsio.log"
|
||||
#define TELEMETRY_FILENAME_ENVAR "MPS_TELEMETRY_FILENAME"
|
||||
|
||||
static EventClock eventTime; /* current event time */
|
||||
static char *prog; /* program name */
|
||||
|
||||
|
||||
/* style: '\0' for human-readable, 'L' for Lisp, 'C' for CDF. */
|
||||
static char style = '\0';
|
||||
|
||||
|
||||
/* everror -- error signalling */
|
||||
|
||||
static void everror(const char *format, ...)
|
||||
|
|
@ -80,7 +68,7 @@ static void everror(const char *format, ...)
|
|||
static void usage(void)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"Usage: %s [-f logfile] [-S[LC]] [-h]\n"
|
||||
"Usage: %s [-f logfile] [-h]\n"
|
||||
"See \"Telemetry\" in the reference manual for instructions.\n",
|
||||
prog);
|
||||
}
|
||||
|
|
@ -99,7 +87,7 @@ static void usageError(void)
|
|||
|
||||
static char *parseArgs(int argc, char *argv[])
|
||||
{
|
||||
char *name = "mpsio.log";
|
||||
char *name = NULL;
|
||||
int i = 1;
|
||||
|
||||
if (argc >= 1)
|
||||
|
|
@ -117,9 +105,6 @@ static char *parseArgs(int argc, char *argv[])
|
|||
else
|
||||
name = argv[i];
|
||||
break;
|
||||
case 'S': /* style */
|
||||
style = argv[i][2]; /* '\0' for human-readable, 'L' for Lisp, */
|
||||
break; /* 'C' for CDF. */
|
||||
case '?': case 'h': /* help */
|
||||
usage();
|
||||
exit(EXIT_SUCCESS);
|
||||
|
|
@ -135,119 +120,39 @@ static char *parseArgs(int argc, char *argv[])
|
|||
|
||||
/* Printing routines */
|
||||
|
||||
static void printHex(ulongest_t val)
|
||||
{
|
||||
printf(" %"PRIXLONGEST, (ulongest_t)val);
|
||||
}
|
||||
|
||||
#define printParamP(p) printHex((ulongest_t)p)
|
||||
#define printParamA(a) printHex((ulongest_t)a)
|
||||
#define printParamU(u) printHex((ulongest_t)u)
|
||||
#define printParamW(w) printHex((ulongest_t)w)
|
||||
#define printParamB(b) printHex((ulongest_t)b)
|
||||
|
||||
/* printStr -- print an EventString */
|
||||
static void printParamD(double d)
|
||||
{
|
||||
printf(" %.10G", d);
|
||||
}
|
||||
|
||||
static void printStr(const char *str, Bool quotes)
|
||||
static void printParamS(const char *str)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (quotes) putchar('"');
|
||||
putchar(' ');
|
||||
putchar('"');
|
||||
for (i = 0; str[i] != '\0'; ++i) {
|
||||
char c = str[i];
|
||||
if (quotes && (c == '"' || c == '\\')) putchar('\\');
|
||||
if (c == '"' || c == '\\') putchar('\\');
|
||||
putchar(c);
|
||||
}
|
||||
if (quotes) putchar('"');
|
||||
putchar('"');
|
||||
}
|
||||
|
||||
|
||||
/* printAddr -- print an Addr or its label */
|
||||
|
||||
static void printAddr(EventProc proc, Addr addr)
|
||||
{
|
||||
UNUSED(proc);
|
||||
printf(style != 'C' ?
|
||||
" %0"PRIwWORD PRIXLONGEST :
|
||||
" %"PRIuLONGEST,
|
||||
(ulongest_t)addr);
|
||||
}
|
||||
|
||||
|
||||
/* printParam* -- printing functions for event parameter types */
|
||||
|
||||
static void printParamA(EventProc proc, char *styleConv, Addr addr)
|
||||
{
|
||||
if (style != 'L') {
|
||||
if (style == 'C') putchar(',');
|
||||
printAddr(proc, addr);
|
||||
} else
|
||||
printf(styleConv, (ulongest_t)addr);
|
||||
}
|
||||
|
||||
static void printParamP(EventProc proc, char *styleConv, void *p)
|
||||
{
|
||||
UNUSED(proc);
|
||||
printf(styleConv, (ulongest_t)p);
|
||||
}
|
||||
|
||||
static void printParamU(EventProc proc, char *styleConv, unsigned u)
|
||||
{
|
||||
UNUSED(proc);
|
||||
printf(styleConv, (ulongest_t)u);
|
||||
}
|
||||
|
||||
static void printParamW(EventProc proc, char *styleConv, Word w)
|
||||
{
|
||||
UNUSED(proc);
|
||||
printf(styleConv, (ulongest_t)w);
|
||||
}
|
||||
|
||||
static void printParamD(EventProc proc, char *styleConv, double d)
|
||||
{
|
||||
UNUSED(proc);
|
||||
UNUSED(styleConv);
|
||||
switch (style) {
|
||||
case '\0':
|
||||
printf(" %#8.3g", d); break;
|
||||
case 'C':
|
||||
printf(", %.10G", d); break;
|
||||
case 'L':
|
||||
printf(" %#.10G", d); break;
|
||||
}
|
||||
}
|
||||
|
||||
static void printParamS(EventProc proc, char *styleConv, const char *s)
|
||||
{
|
||||
UNUSED(proc);
|
||||
UNUSED(styleConv);
|
||||
if (style == 'C') putchar(',');
|
||||
putchar(' ');
|
||||
printStr(s, (style == 'C' || style == 'L'));
|
||||
}
|
||||
|
||||
static void printParamB(EventProc proc, char *styleConv, Bool b)
|
||||
{
|
||||
UNUSED(proc);
|
||||
printf(styleConv, (ulongest_t)b);
|
||||
}
|
||||
|
||||
|
||||
/* readLog -- read and parse log
|
||||
*
|
||||
* This is the heart of eventcnv: It reads an event log using
|
||||
* EventRead. It updates the counters. It looks up the format,
|
||||
* parses the arguments, and prints a representation of the event.
|
||||
* Each argument is printed using printArg (see RELATION, below),
|
||||
* except for some event types that are handled specially.
|
||||
*/
|
||||
/* readLog -- read and parse log */
|
||||
|
||||
static void readLog(EventProc proc)
|
||||
{
|
||||
char *styleConv = NULL; /* suppress uninit warning */
|
||||
|
||||
/* Init style. */
|
||||
switch (style) {
|
||||
case '\0':
|
||||
styleConv = " %8"PRIXLONGEST; break;
|
||||
case 'C':
|
||||
styleConv = ", %"PRIuLONGEST; break;
|
||||
case 'L':
|
||||
styleConv = " %"PRIXLONGEST; break;
|
||||
default:
|
||||
everror("Unknown style code '%c'", style);
|
||||
}
|
||||
|
||||
while (TRUE) { /* loop for each event */
|
||||
Event event;
|
||||
EventCode code;
|
||||
|
|
@ -260,128 +165,25 @@ static void readLog(EventProc proc)
|
|||
eventTime = event->any.clock;
|
||||
code = event->any.code;
|
||||
|
||||
/* Output event. */
|
||||
{
|
||||
if (style == 'L') putchar('(');
|
||||
EVENT_CLOCK_PRINT(stdout, eventTime);
|
||||
printf(" %X", (unsigned)code);
|
||||
|
||||
switch (style) {
|
||||
case '\0': case 'L':
|
||||
EVENT_CLOCK_PRINT(stdout, eventTime);
|
||||
putchar(' ');
|
||||
break;
|
||||
case 'C':
|
||||
EVENT_CLOCK_PRINT(stdout, eventTime);
|
||||
fputs(", ", stdout);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (style) {
|
||||
case '\0': case 'L': {
|
||||
printf("%-19s ", EventCode2Name(code));
|
||||
} break;
|
||||
case 'C':
|
||||
printf("%u", (unsigned)code);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (code) {
|
||||
|
||||
case EventLabelCode:
|
||||
switch (style) {
|
||||
case '\0': case 'C':
|
||||
{
|
||||
const char *sym = LabelText(proc, event->Label.f1);
|
||||
printf(style == '\0' ?
|
||||
" %08"PRIXLONGEST" " :
|
||||
", %"PRIuLONGEST", ",
|
||||
(ulongest_t)event->Label.f0);
|
||||
if (sym != NULL) {
|
||||
printStr(sym, (style == 'C'));
|
||||
} else {
|
||||
printf(style == '\0' ?
|
||||
"sym %05"PRIXLONGEST :
|
||||
"sym %"PRIXLONGEST"\"",
|
||||
(ulongest_t)event->Label.f1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'L':
|
||||
printf(" %"PRIXLONGEST" %"PRIXLONGEST,
|
||||
(ulongest_t)event->Label.f0,
|
||||
(ulongest_t)event->Label.f1);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case EventMeterValuesCode:
|
||||
switch (style) {
|
||||
case '\0':
|
||||
if (event->MeterValues.f3 == 0) {
|
||||
printf(" %08"PRIXLONGEST" 0 N/A N/A N/A N/A",
|
||||
(ulongest_t)event->MeterValues.f0);
|
||||
} else {
|
||||
double mean = event->MeterValues.f1 / (double)event->MeterValues.f3;
|
||||
/* .stddev: stddev = sqrt(meanSquared - mean^2), but see */
|
||||
/* <code/meter.c#limitation.variance>. */
|
||||
double stddev = sqrt(fabs(event->MeterValues.f2
|
||||
- (mean * mean)));
|
||||
printf(" %08"PRIXLONGEST" %8u %8u %8u %#8.3g %#8.3g",
|
||||
(ulongest_t)event->MeterValues.f0, (uint)event->MeterValues.f3,
|
||||
(uint)event->MeterValues.f4, (uint)event->MeterValues.f5,
|
||||
mean, stddev);
|
||||
}
|
||||
printAddr(proc, (Addr)event->MeterValues.f0);
|
||||
break;
|
||||
|
||||
case 'C':
|
||||
putchar(',');
|
||||
printAddr(proc, (Addr)event->MeterValues.f0);
|
||||
printf(", %.10G, %.10G, %u, %u, %u",
|
||||
event->MeterValues.f1, event->MeterValues.f2,
|
||||
(uint)event->MeterValues.f3, (uint)event->MeterValues.f4,
|
||||
(uint)event->MeterValues.f5);
|
||||
break;
|
||||
|
||||
case 'L':
|
||||
printf(" %"PRIXLONGEST" %#.10G %#.10G %X %X %X",
|
||||
(ulongest_t)event->MeterValues.f0,
|
||||
event->MeterValues.f1, event->MeterValues.f2,
|
||||
(uint)event->MeterValues.f3, (uint)event->MeterValues.f4,
|
||||
(uint)event->MeterValues.f5);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case EventPoolInitCode: /* pool, arena, class */
|
||||
printf(styleConv, (ulongest_t)event->PoolInit.f0);
|
||||
printf(styleConv, (ulongest_t)event->PoolInit.f1);
|
||||
/* class is a Pointer, but we label them, so call printAddr */
|
||||
if (style != 'L') {
|
||||
if (style == 'C') putchar(',');
|
||||
printAddr(proc, (Addr)event->PoolInit.f2);
|
||||
} else
|
||||
printf(styleConv, (ulongest_t)event->PoolInit.f2);
|
||||
break;
|
||||
|
||||
default:
|
||||
switch (code) {
|
||||
#define EVENT_PARAM_PRINT(name, index, sort, ident) \
|
||||
printParam##sort(proc, styleConv, event->name.f##index);
|
||||
printParam##sort(event->name.f##index);
|
||||
#define EVENT_PRINT(X, name, code, always, kind) \
|
||||
case code: \
|
||||
EVENT_##name##_PARAMS(EVENT_PARAM_PRINT, name) \
|
||||
break;
|
||||
switch (code) { EVENT_LIST(EVENT_PRINT, X) }
|
||||
}
|
||||
|
||||
if (style == 'L') putchar(')');
|
||||
putchar('\n');
|
||||
fflush(stdout);
|
||||
EVENT_LIST(EVENT_PRINT, X)
|
||||
}
|
||||
|
||||
putchar('\n');
|
||||
fflush(stdout);
|
||||
EventDestroy(proc, event);
|
||||
} /* while(!feof(input)) */
|
||||
}
|
||||
|
||||
|
||||
/* logReader -- reader function for a file log */
|
||||
|
||||
static FILE *input;
|
||||
|
|
@ -413,9 +215,14 @@ int main(int argc, char *argv[])
|
|||
assert(CHECKCONV(ulongest_t, Addr));
|
||||
assert(CHECKCONV(ulongest_t, void *));
|
||||
assert(CHECKCONV(ulongest_t, EventCode));
|
||||
assert(CHECKCONV(Addr, void *)); /* for labelled pointers */
|
||||
|
||||
filename = parseArgs(argc, argv);
|
||||
if (!filename) {
|
||||
filename = getenv(TELEMETRY_FILENAME_ENVAR
|
||||
);
|
||||
if(!filename)
|
||||
filename = DEFAULT_TELEMETRY_FILENAME;
|
||||
}
|
||||
|
||||
if (strcmp(filename, "-") == 0)
|
||||
input = stdin;
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
|
||||
#define EVENT_VERSION_MAJOR ((unsigned)1)
|
||||
#define EVENT_VERSION_MEDIAN ((unsigned)0)
|
||||
#define EVENT_VERSION_MINOR ((unsigned)0)
|
||||
#define EVENT_VERSION_MINOR ((unsigned)1)
|
||||
|
||||
|
||||
/* EVENT_LIST -- list of event types and general properties
|
||||
|
|
@ -68,7 +68,7 @@
|
|||
*/
|
||||
|
||||
#define EventNameMAX ((size_t)19)
|
||||
#define EventCodeMAX ((EventCode)0x0073)
|
||||
#define EventCodeMAX ((EventCode)0x0074)
|
||||
|
||||
#define EVENT_LIST(EVENT, X) \
|
||||
/* 0123456789012345678 <- don't exceed without changing EventNameMAX */ \
|
||||
|
|
@ -179,7 +179,8 @@
|
|||
EVENT(X, TraceFindGrey , 0x0070, TRUE, Trace) \
|
||||
EVENT(X, TraceBandAdvance , 0x0071, TRUE, Trace) \
|
||||
EVENT(X, AWLDeclineTotal , 0x0072, TRUE, Trace) \
|
||||
EVENT(X, AWLDeclineSeg , 0x0073, TRUE, Trace)
|
||||
EVENT(X, AWLDeclineSeg , 0x0073, TRUE, Trace) \
|
||||
EVENT(X, EventInit , 0x0074, TRUE, Arena)
|
||||
|
||||
|
||||
/* Remember to update EventNameMAX and EventCodeMAX in eventcom.h!
|
||||
|
|
@ -620,6 +621,14 @@
|
|||
PARAM(X, 0, P, seg) /* segment declined single access */ \
|
||||
PARAM(X, 1, W, singleAccesses) /* single accesses this cycle */
|
||||
|
||||
#define EVENT_EventInit_PARAMS(PARAM, X) \
|
||||
PARAM(X, 0, U, major) /* EVENT_VERSION_MAJOR */ \
|
||||
PARAM(X, 1, U, median) /* EVENT_VERSION_MEDIAN */ \
|
||||
PARAM(X, 2, U, minor) /* EVENT_VERSION_MINOR */ \
|
||||
PARAM(X, 3, U, maxCode) /* EventCodeMAX */ \
|
||||
PARAM(X, 4, U, maxNameLen) /* EventNameMAX */ \
|
||||
PARAM(X, 5, U, wordWidth) /* MPS_WORD_WIDTH */
|
||||
|
||||
|
||||
#endif /* eventdef_h */
|
||||
|
||||
|
|
|
|||
950
mps/code/eventsql.c
Normal file
950
mps/code/eventsql.c
Normal file
|
|
@ -0,0 +1,950 @@
|
|||
/* eventsql.c: event log to SQLite importer.
|
||||
*
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (c) 2012 Ravenbrook Limited. See end of file for license.
|
||||
*
|
||||
* This is a command-line tool that imports events from a text-format
|
||||
* MPS telemetry file into a SQLite database file.
|
||||
*
|
||||
* The default MPS library will write a binary-format telemetry file.
|
||||
* The binary-format file can be converted into a text-format file
|
||||
* using the eventcnv program with the -SC -v options. For
|
||||
* binary-format compatibility, eventcnv has to be run on the same
|
||||
* platform as the MPS.
|
||||
*
|
||||
* These ASCII text-format files have one line per event, and can be
|
||||
* manipulated by various splendid systems in the usual Unix way. This
|
||||
* eventsql program is one such.
|
||||
*
|
||||
* Note that eventsql can read streams that come from an MPS running
|
||||
* on another platform. This is another reason why we use the
|
||||
* intermediate text-format file, rather than reading the
|
||||
* binary-format file directly: you can run the MPS and then eventcnv
|
||||
* on the target platform, then optionally analyse the resulting text
|
||||
* file on some other machine.
|
||||
*
|
||||
* Each event type gets its own table in the database. These tables
|
||||
* are created from the definitions in eventdef.h if they don't
|
||||
* already exist. Each event becomes a single row in the appropriate
|
||||
* table, which has a column for each event parameter, a time column
|
||||
* for the event time field, and a log_serial column to identify the
|
||||
* log file. Because the database schema depends on the event
|
||||
* definitions in eventdef.h, eventsql has to be compiled using the
|
||||
* same event header files as those used to compile the MPS and
|
||||
* eventcnv which generated and processed the telemetry output.
|
||||
*
|
||||
* The program also creates several other tables: three 'glue' tables
|
||||
* containing event metadata - event_kind (one row per kind),
|
||||
* event_type (one row per type), and event_param (one row per
|
||||
* parameter), all derived from eventdef.h - and the event_log table
|
||||
* which has one row per log file imported (the log_serial column in
|
||||
* the event tables is a primary key to this event_log table).
|
||||
*
|
||||
* No tables are created if they already exist, unless the -r
|
||||
* (rebuild) switch is given.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* -v (verbose): Increase verbosity. eventsql logs to stderr. By
|
||||
* default, it doesn't log much; it can be made more and more
|
||||
* loquacious by adding more -v switches. Given one or more -v
|
||||
* switches, it also writes 'ticks' (periods) to stdout, to show
|
||||
* progress (one dot is 100k events).
|
||||
*
|
||||
* -t (test): Run unit tests on parts of eventsql. There aren't many
|
||||
* of these. TODO: write more unit tests.
|
||||
*
|
||||
* -f (force): Import the events to SQL even if the SQL database
|
||||
* already includes a record of importing a matching log file.
|
||||
*
|
||||
* -r (rebuild): Drop the glue tables from SQL, which will force them
|
||||
* to be recreated. Important if you change event types or kinds in
|
||||
* eventdef.h.
|
||||
*
|
||||
* -l <logfile>: Import events from the named logfile. Defaults to
|
||||
* stdin. If the specified file (matched by size and modtime) has
|
||||
* previously been imported to the same database, it will not be
|
||||
* imported again unless -f is specified.
|
||||
*
|
||||
* -d <database>: Import events to the named database file. If not
|
||||
* specified, eventsql will use the MPS_EVENT_DATABASE environment
|
||||
* variable, and default to mpsevent.db.
|
||||
*
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
#include "misc.h"
|
||||
#include "config.h"
|
||||
#include "eventdef.h"
|
||||
#include "eventcom.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sqlite3.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#define DATABASE_NAME_ENVAR "MPS_EVENT_DATABASE"
|
||||
#define DEFAULT_DATABASE_NAME "mpsevent.db"
|
||||
|
||||
typedef sqlite3_int64 int64;
|
||||
|
||||
/* we output rows of dots. One dot per SMALL_TICK events,
|
||||
* BIG_TICK dots per row. */
|
||||
|
||||
#define SMALL_TICK 100000
|
||||
#define BIG_TICK 50
|
||||
|
||||
/* Utility code for logging to stderr with multiple log levels,
|
||||
* and for reporting errors.
|
||||
*/
|
||||
|
||||
unsigned int verbosity = 0;
|
||||
|
||||
#define LOG_ALWAYS 0
|
||||
#define LOG_OFTEN 1
|
||||
#define LOG_SOMETIMES 2
|
||||
#define LOG_SELDOM 3
|
||||
#define LOG_RARELY 4
|
||||
|
||||
static void vlog(unsigned int level, const char *format, va_list args)
|
||||
{
|
||||
if (level <= verbosity) {
|
||||
fflush(stderr); /* sync */
|
||||
fprintf(stderr, "log %d: ", level);
|
||||
vfprintf(stderr, format, args);
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void log(unsigned int level, const char *format, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vlog(level, format, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
static void error(const char *format, ...)
|
||||
{
|
||||
va_list args;
|
||||
fprintf(stderr, "Fatal error: ");
|
||||
va_start(args, format);
|
||||
vlog(LOG_ALWAYS, format, args);
|
||||
va_end(args);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void sqlite_error(int res, sqlite3 *db, const char *format, ...)
|
||||
{
|
||||
va_list args;
|
||||
log(LOG_ALWAYS, "Fatal SQL error %d", res);
|
||||
va_start(args, format);
|
||||
vlog(LOG_ALWAYS, format, args);
|
||||
va_end(args);
|
||||
log(LOG_ALWAYS, "SQLite message: %s\n", sqlite3_errmsg(db));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static char *prog; /* program name */
|
||||
static int rebuild = FALSE;
|
||||
static int runTests = FALSE;
|
||||
static int force = FALSE;
|
||||
static char *databaseName = NULL;
|
||||
static char *logFileName = NULL;
|
||||
|
||||
static void usage(void)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"Usage: %s [-rtf] [-l <logfile>] [-d <database>] [-v -v -v ...]\n",
|
||||
prog);
|
||||
}
|
||||
|
||||
static void usageError(void)
|
||||
{
|
||||
usage();
|
||||
error("Bad usage");
|
||||
}
|
||||
|
||||
/* parseArgs -- parse command line arguments */
|
||||
|
||||
static void parseArgs(int argc, char *argv[])
|
||||
{
|
||||
int i = 1;
|
||||
|
||||
if (argc >= 1)
|
||||
prog = argv[0];
|
||||
else
|
||||
prog = "unknown";
|
||||
|
||||
while (i < argc) { /* consider argument i */
|
||||
if (argv[i][0] == '-') { /* it's an option argument */
|
||||
switch (argv[i][1]) {
|
||||
case 'v': /* verbosity */
|
||||
++ verbosity;
|
||||
break;
|
||||
case 'r': /* rebuild */
|
||||
rebuild = TRUE;
|
||||
break;
|
||||
case 'f': /* force */
|
||||
force = TRUE;
|
||||
break;
|
||||
case 't': /* run tests */
|
||||
runTests = TRUE;
|
||||
break;
|
||||
case 'l': /* log file name */
|
||||
++ i;
|
||||
if (i == argc)
|
||||
usageError();
|
||||
else
|
||||
logFileName = argv[i];
|
||||
break;
|
||||
case 'd': /* database file name */
|
||||
++ i;
|
||||
if (i == argc)
|
||||
usageError();
|
||||
else
|
||||
databaseName = argv[i];
|
||||
break;
|
||||
case '?': case 'h': /* help */
|
||||
usage();
|
||||
break;
|
||||
default:
|
||||
usageError();
|
||||
}
|
||||
} /* if option */
|
||||
++ i;
|
||||
}
|
||||
}
|
||||
|
||||
/* openDatabase(p) opens the database file and returns a SQLite 3
|
||||
* database connection object. */
|
||||
|
||||
static sqlite3 *openDatabase(void)
|
||||
{
|
||||
sqlite3 *db;
|
||||
int res;
|
||||
|
||||
if (!databaseName) {
|
||||
databaseName = getenv(DATABASE_NAME_ENVAR);
|
||||
if(!databaseName)
|
||||
databaseName = DEFAULT_DATABASE_NAME;
|
||||
}
|
||||
res = sqlite3_open_v2(databaseName,
|
||||
&db,
|
||||
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
|
||||
NULL); /* use default sqlite_vfs object */
|
||||
|
||||
if (res != SQLITE_OK)
|
||||
sqlite_error(res, db, "Opening %s failed", databaseName);
|
||||
|
||||
log(LOG_OFTEN, "Writing to %s.",databaseName);
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
/* closeDatabase(db) closes the database opened by openDatabase(). */
|
||||
|
||||
static void closeDatabase(sqlite3 *db)
|
||||
{
|
||||
int res = sqlite3_close(db);
|
||||
if (res != SQLITE_OK)
|
||||
sqlite_error(res, db, "Closing database failed");
|
||||
log(LOG_SOMETIMES, "Closed %s.", databaseName);
|
||||
}
|
||||
|
||||
/* Utility functions for SQLite statements. */
|
||||
|
||||
static sqlite3_stmt *prepareStatement(sqlite3 *db,
|
||||
const char *sql)
|
||||
{
|
||||
int res;
|
||||
sqlite3_stmt *statement;
|
||||
log(LOG_SELDOM, "Preparing statement %s", sql);
|
||||
res = sqlite3_prepare_v2(db, sql,
|
||||
-1, /* prepare whole string as statement */
|
||||
&statement,
|
||||
NULL);
|
||||
if (res != SQLITE_OK)
|
||||
sqlite_error(res, db, "statement preparation failed: %s", sql);
|
||||
return statement;
|
||||
}
|
||||
|
||||
static void finalizeStatement(sqlite3 *db,
|
||||
sqlite3_stmt *statement)
|
||||
{
|
||||
int res;
|
||||
res = sqlite3_finalize(statement);
|
||||
if (res != SQLITE_OK)
|
||||
sqlite_error(res, db, "statement finalize failed");
|
||||
}
|
||||
|
||||
static void runStatement(sqlite3 *db,
|
||||
const char *sql,
|
||||
const char *description)
|
||||
{
|
||||
int res;
|
||||
log(LOG_SELDOM, "%s: %s", description, sql);
|
||||
res = sqlite3_exec(db,
|
||||
sql,
|
||||
NULL, /* No callback */
|
||||
NULL, /* No callback closure */
|
||||
NULL); /* error messages handled by sqlite_error */
|
||||
if (res != SQLITE_OK)
|
||||
sqlite_error(res, db, "%s failed - statement %s", description, sql);
|
||||
}
|
||||
|
||||
/* Test for the existence of a table using sqlite_master table.
|
||||
*/
|
||||
|
||||
static int tableExists(sqlite3* db, const char *tableName)
|
||||
{
|
||||
int res;
|
||||
int exists;
|
||||
sqlite3_stmt *statement;
|
||||
|
||||
statement = prepareStatement(db,
|
||||
"SELECT 1 FROM sqlite_master WHERE type='table' AND name=?");
|
||||
res = sqlite3_bind_text(statement, 1, tableName, -1, SQLITE_STATIC);
|
||||
if (res != SQLITE_OK)
|
||||
sqlite_error(res, db, "table existence bind of name failed.");
|
||||
res = sqlite3_step(statement);
|
||||
switch(res) {
|
||||
case SQLITE_DONE:
|
||||
exists = 0;
|
||||
break;
|
||||
case SQLITE_ROW:
|
||||
exists = 1;
|
||||
break;
|
||||
default:
|
||||
sqlite_error(res, db, "select from sqlite_master failed.");
|
||||
}
|
||||
finalizeStatement(db, statement);
|
||||
return exists;
|
||||
}
|
||||
|
||||
/* Unit test for tableExists() */
|
||||
|
||||
static struct {
|
||||
const char* name;
|
||||
int exists;
|
||||
} tableTests[] = {
|
||||
{"event_kind", TRUE},
|
||||
{"spong", FALSE},
|
||||
{"EVENT_SegSplit", TRUE}
|
||||
};
|
||||
|
||||
static void testTableExists(sqlite3 *db)
|
||||
{
|
||||
size_t i;
|
||||
int defects = 0;
|
||||
int tests = 0;
|
||||
for (i=0; i < (sizeof(tableTests)/sizeof(tableTests[0])); ++i) {
|
||||
const char *name = tableTests[i].name;
|
||||
int exists = tableExists(db, name);
|
||||
if (exists)
|
||||
log(LOG_OFTEN, "Table exists: %s", name);
|
||||
else
|
||||
log(LOG_OFTEN, "Table does not exist: %s", name);
|
||||
if (exists != tableTests[i].exists) {
|
||||
log(LOG_ALWAYS, "tableExists test failed on table %s", name);
|
||||
++ defects;
|
||||
}
|
||||
++ tests;
|
||||
}
|
||||
log(LOG_ALWAYS, "%d tests, %d defects found.", tests, defects);
|
||||
}
|
||||
|
||||
/* Every time we put events from a log file into a database file, we
|
||||
* add the log file to the event_log table, and get a serial number
|
||||
* from SQL which is then attached to all event rows from that log.
|
||||
* We use this to record overall SQL activity, to deter mistaken
|
||||
* attempts to add the same log file twice, and to allow events from
|
||||
* several different log files to share the same SQL file.
|
||||
*
|
||||
* When reading events from stdin, we can't so easily avoid the
|
||||
* duplication (unless we, e.g., take a hash of the event set); we
|
||||
* assume that the user is smart enough not to do that.
|
||||
*/
|
||||
|
||||
static int64 logSerial = 0;
|
||||
|
||||
static void registerLogFile(sqlite3 *db,
|
||||
const char *filename)
|
||||
{
|
||||
sqlite3_stmt *statement;
|
||||
int res;
|
||||
const unsigned char *name;
|
||||
int64 completed;
|
||||
int64 file_size;
|
||||
int64 file_modtime;
|
||||
|
||||
if (filename) {
|
||||
struct stat st;
|
||||
res = stat(filename, &st);
|
||||
if (res != 0)
|
||||
error("Couldn't stat() %s", filename);
|
||||
file_size = st.st_size;
|
||||
file_modtime = st.st_mtime;
|
||||
|
||||
statement = prepareStatement(db,
|
||||
"SELECT name, serial, completed FROM event_log"
|
||||
" WHERE size = ? AND modtime = ?");
|
||||
res = sqlite3_bind_int64(statement, 1, file_size);
|
||||
if (res != SQLITE_OK)
|
||||
sqlite_error(res, db, "event_log bind of size failed.");
|
||||
res = sqlite3_bind_int64(statement, 2, file_modtime);
|
||||
if (res != SQLITE_OK)
|
||||
sqlite_error(res, db, "event_log bind of modtime failed.");
|
||||
res = sqlite3_step(statement);
|
||||
switch(res) {
|
||||
case SQLITE_DONE:
|
||||
log(LOG_SOMETIMES, "No log file matching '%s' found in database.", filename);
|
||||
break;
|
||||
case SQLITE_ROW:
|
||||
name = sqlite3_column_text(statement, 0);
|
||||
logSerial = sqlite3_column_int64(statement, 1);
|
||||
completed = sqlite3_column_int64(statement, 2);
|
||||
log(force ? LOG_OFTEN : LOG_ALWAYS, "Log file matching '%s' already in event_log, named \"%s\" (serial %lu, completed %lu).",
|
||||
filename, name, logSerial, completed);
|
||||
if (force) {
|
||||
log(LOG_OFTEN, "Continuing anyway because -f specified.");
|
||||
} else {
|
||||
log(LOG_ALWAYS, "Exiting. Specify -f to force events into SQL anyway.");
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
sqlite_error(res, db, "select from event_log failed.");
|
||||
}
|
||||
finalizeStatement(db, statement);
|
||||
} else { /* stdin */
|
||||
filename = "<stdin>";
|
||||
file_size = 0;
|
||||
file_modtime = 0;
|
||||
}
|
||||
statement = prepareStatement(db,
|
||||
"INSERT into event_log (name, size, modtime, completed)"
|
||||
" VALUES (?, ?, ?, 0)");
|
||||
res = sqlite3_bind_text(statement, 1, filename, -1, SQLITE_STATIC);
|
||||
if (res != SQLITE_OK)
|
||||
sqlite_error(res, db, "event_log insert bind of name failed.");
|
||||
res = sqlite3_bind_int64(statement, 2, file_size);
|
||||
if (res != SQLITE_OK)
|
||||
sqlite_error(res, db, "event_log insert bind of size failed.");
|
||||
res = sqlite3_bind_int64(statement, 3, file_modtime);
|
||||
if (res != SQLITE_OK)
|
||||
sqlite_error(res, db, "event_log insert bind of modtime failed.");
|
||||
res = sqlite3_step(statement);
|
||||
if (res != SQLITE_DONE)
|
||||
sqlite_error(res, db, "insert into event_log failed.");
|
||||
logSerial = sqlite3_last_insert_rowid(db);
|
||||
log(LOG_SOMETIMES, "Log file %s added to event_log with serial %lu",
|
||||
filename, logSerial);
|
||||
finalizeStatement(db, statement);
|
||||
}
|
||||
|
||||
static void logFileCompleted(sqlite3 *db,
|
||||
int64 completed)
|
||||
{
|
||||
sqlite3_stmt *statement;
|
||||
int res;
|
||||
|
||||
statement = prepareStatement(db,
|
||||
"UPDATE event_log SET completed=? WHERE serial=?");
|
||||
res = sqlite3_bind_int64(statement, 2, logSerial);
|
||||
if (res != SQLITE_OK)
|
||||
sqlite_error(res, db, "event_log update bind of serial failed.");
|
||||
res = sqlite3_bind_int64(statement, 1, completed);
|
||||
if (res != SQLITE_OK)
|
||||
sqlite_error(res, db, "event_log update bind of completed failed.");
|
||||
res = sqlite3_step(statement);
|
||||
if (res != SQLITE_DONE)
|
||||
sqlite_error(res, db, "insert into event_log failed.");
|
||||
log(LOG_SOMETIMES, "Marked in event_log: %lu events", completed);
|
||||
finalizeStatement(db, statement);
|
||||
}
|
||||
|
||||
/* Macro magic to make a CREATE TABLE statement for each event type. */
|
||||
|
||||
#define EVENT_PARAM_SQL_TYPE_A "INTEGER"
|
||||
#define EVENT_PARAM_SQL_TYPE_P "INTEGER"
|
||||
#define EVENT_PARAM_SQL_TYPE_U "INTEGER"
|
||||
#define EVENT_PARAM_SQL_TYPE_W "INTEGER"
|
||||
#define EVENT_PARAM_SQL_TYPE_D "REAL "
|
||||
#define EVENT_PARAM_SQL_TYPE_S "TEXT "
|
||||
#define EVENT_PARAM_SQL_TYPE_B "INTEGER"
|
||||
|
||||
#define EVENT_PARAM_SQL_COLUMN(X, index, sort, ident) \
|
||||
"\"" #ident "\" " EVENT_PARAM_SQL_TYPE_##sort ", "
|
||||
|
||||
#define EVENT_TABLE_CREATE(X, name, code, always, kind) \
|
||||
"CREATE TABLE IF NOT EXISTS EVENT_" #name " ( " \
|
||||
EVENT_##name##_PARAMS(EVENT_PARAM_SQL_COLUMN, X) \
|
||||
"time INTEGER, " \
|
||||
"log_serial INTEGER)",
|
||||
|
||||
/* An array of table-creation statement strings. */
|
||||
|
||||
const char *createStatements[] = {
|
||||
"CREATE TABLE IF NOT EXISTS event_kind (name TEXT,"
|
||||
" description TEXT,"
|
||||
" enum INTEGER PRIMARY KEY)",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS event_type (name TEXT,"
|
||||
" code INTEGER PRIMARY KEY,"
|
||||
" always INTEGER,"
|
||||
" kind INTEGER,"
|
||||
" FOREIGN KEY (kind) REFERENCES event_kind(enum));",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS event_param (type INTEGER,"
|
||||
" param_index INTEGER,"
|
||||
" sort TEXT,"
|
||||
" ident TEXT,"
|
||||
" FOREIGN KEY (type) REFERENCES event_type(code));",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS event_log (name TEXT,"
|
||||
" size INTEGER,"
|
||||
" modtime INTEGER,"
|
||||
" completed INTEGER,"
|
||||
" serial INTEGER PRIMARY KEY AUTOINCREMENT)",
|
||||
|
||||
EVENT_LIST(EVENT_TABLE_CREATE, X)
|
||||
};
|
||||
|
||||
/* makeTables makes all the tables. */
|
||||
|
||||
static void makeTables(sqlite3 *db)
|
||||
{
|
||||
size_t i;
|
||||
log(LOG_SOMETIMES, "Creating tables.");
|
||||
|
||||
for (i=0; i < (sizeof(createStatements)/sizeof(createStatements[0])); ++i) {
|
||||
runStatement(db, createStatements[i], "Table creation");
|
||||
}
|
||||
}
|
||||
|
||||
const char *glueTables[] = {
|
||||
"event_kind",
|
||||
"event_type",
|
||||
"event_param",
|
||||
};
|
||||
|
||||
static void dropGlueTables(sqlite3 *db)
|
||||
{
|
||||
size_t i;
|
||||
int res;
|
||||
char sql[1024];
|
||||
|
||||
log(LOG_ALWAYS, "Dropping glue tables so they are rebuilt.");
|
||||
|
||||
for (i=0; i < (sizeof(glueTables)/sizeof(glueTables[0])); ++i) {
|
||||
log(LOG_SOMETIMES, "Dropping table %s", glueTables[i]);
|
||||
sprintf(sql, "DROP TABLE %s", glueTables[i]);
|
||||
res = sqlite3_exec(db,
|
||||
sql,
|
||||
NULL, /* No callback */
|
||||
NULL, /* No callback closure */
|
||||
NULL); /* error messages handled by sqlite_error */
|
||||
/* Don't check for errors. */
|
||||
}
|
||||
}
|
||||
|
||||
/* Populate the metadata "glue" tables event_kind, event_type, and
|
||||
* event_param. */
|
||||
|
||||
#define EVENT_KIND_DO_INSERT(X, name, description) \
|
||||
res = sqlite3_bind_text(statement, 1, #name, -1, SQLITE_STATIC); \
|
||||
if (res != SQLITE_OK) \
|
||||
sqlite_error(res, db, "event_kind bind of name \"" #name "\" failed."); \
|
||||
res = sqlite3_bind_text(statement, 2, description, -1, SQLITE_STATIC); \
|
||||
if (res != SQLITE_OK) \
|
||||
sqlite_error(res, db, "event_kind bind of description \"" description "\" failed."); \
|
||||
res = sqlite3_bind_int(statement, 3, i); \
|
||||
if (res != SQLITE_OK) \
|
||||
sqlite_error(res, db, "event_kind bind of enum %d failed.", i); \
|
||||
++i; \
|
||||
res = sqlite3_step(statement); \
|
||||
if (res != SQLITE_DONE) \
|
||||
sqlite_error(res, db, "event_kind insert of name \"" #name "\" failed."); \
|
||||
if (sqlite3_changes(db) != 0) \
|
||||
log(LOG_SOMETIMES, "Insert of event_kind row for \"" #name "\" affected %d rows.", sqlite3_changes(db)); \
|
||||
res = sqlite3_reset(statement); \
|
||||
if (res != SQLITE_OK) \
|
||||
sqlite_error(res, db, "Couldn't reset event_kind insert statement.");
|
||||
|
||||
#define EVENT_TYPE_DO_INSERT(X, name, code, always, kind) \
|
||||
res = sqlite3_bind_text(statement, 1, #name, -1, SQLITE_STATIC); \
|
||||
if (res != SQLITE_OK) \
|
||||
sqlite_error(res, db, "event_type bind of name \"" #name "\" failed."); \
|
||||
res = sqlite3_bind_int(statement, 2, code); \
|
||||
if (res != SQLITE_OK) \
|
||||
sqlite_error(res, db, "event_type bind of code %d failed.", code); \
|
||||
res = sqlite3_bind_int(statement, 3, always); \
|
||||
if (res != SQLITE_OK) \
|
||||
sqlite_error(res, db, "event_type bind of always for name \"" #name "\" failed."); \
|
||||
res = sqlite3_bind_int(statement, 4, EventKind##kind); \
|
||||
if (res != SQLITE_OK) \
|
||||
sqlite_error(res, db, "event_type bind of kind for name \"" #name "\" failed."); \
|
||||
res = sqlite3_step(statement); \
|
||||
if (res != SQLITE_DONE) \
|
||||
sqlite_error(res, db, "event_type insert of name \"" #name "\" failed."); \
|
||||
if (sqlite3_changes(db) != 0) \
|
||||
log(LOG_SOMETIMES, "Insert of event_type row for \"" #name "\" affected %d rows.", sqlite3_changes(db)); \
|
||||
res = sqlite3_reset(statement); \
|
||||
if (res != SQLITE_OK) \
|
||||
sqlite_error(res, db, "Couldn't reset event_type insert statement.");
|
||||
|
||||
#define EVENT_PARAM_DO_INSERT(code, index, sort, ident) \
|
||||
res = sqlite3_bind_int(statement, 1, code); \
|
||||
if (res != SQLITE_OK) \
|
||||
sqlite_error(res, db, "event_param bind of code %d failed.", code); \
|
||||
res = sqlite3_bind_int(statement, 2, index); \
|
||||
if (res != SQLITE_OK) \
|
||||
sqlite_error(res, db, "event_param bind of index %d failed.", index); \
|
||||
res = sqlite3_bind_text(statement, 3, #sort, -1, SQLITE_STATIC); \
|
||||
if (res != SQLITE_OK) \
|
||||
sqlite_error(res, db, "event_type bind of sort \"" #sort "\" failed."); \
|
||||
res = sqlite3_bind_text(statement, 4, #ident, -1, SQLITE_STATIC); \
|
||||
if (res != SQLITE_OK) \
|
||||
sqlite_error(res, db, "event_type bind of ident \"" #ident "\" failed."); \
|
||||
res = sqlite3_step(statement); \
|
||||
if (res != SQLITE_DONE) \
|
||||
sqlite_error(res, db, "event_param insert of ident \"" #ident "\" for code %d failed.", code); \
|
||||
if (sqlite3_changes(db) != 0) \
|
||||
log(LOG_SOMETIMES, "Insert of event_param row for code %d, ident \"" #ident "\" affected %d rows.", code, sqlite3_changes(db)); \
|
||||
res = sqlite3_reset(statement); \
|
||||
if (res != SQLITE_OK) \
|
||||
sqlite_error(res, db, "Couldn't reset event_param insert statement.");
|
||||
|
||||
#define EVENT_TYPE_INSERT_PARAMS(X, name, code, always, kind) \
|
||||
EVENT_##name##_PARAMS(EVENT_PARAM_DO_INSERT, code)
|
||||
|
||||
static void fillGlueTables(sqlite3 *db)
|
||||
{
|
||||
int i;
|
||||
sqlite3_stmt *statement;
|
||||
int res;
|
||||
|
||||
statement = prepareStatement(db,
|
||||
"INSERT OR IGNORE INTO event_kind (name, description, enum)"
|
||||
"VALUES (?, ?, ?)");
|
||||
|
||||
i = 0;
|
||||
EventKindENUM(EVENT_KIND_DO_INSERT, X);
|
||||
|
||||
finalizeStatement(db, statement);
|
||||
|
||||
statement = prepareStatement(db,
|
||||
"INSERT OR IGNORE INTO event_type (name, code, always, kind)"
|
||||
"VALUES (?, ?, ?, ?)");
|
||||
EVENT_LIST(EVENT_TYPE_DO_INSERT, X);
|
||||
|
||||
finalizeStatement(db, statement);
|
||||
|
||||
statement = prepareStatement(db,
|
||||
"INSERT OR IGNORE INTO event_param (type, param_index, sort, ident)"
|
||||
"VALUES (?, ?, ?, ?)");
|
||||
EVENT_LIST(EVENT_TYPE_INSERT_PARAMS, X);
|
||||
|
||||
finalizeStatement(db, statement);
|
||||
}
|
||||
|
||||
/* Populate the actual event tables. */
|
||||
|
||||
#define EVENT_TYPE_DECLARE_STATEMENT(X, name, code, always, kind) \
|
||||
sqlite3_stmt *stmt_##name;
|
||||
|
||||
#define EVENT_PARAM_PREPARE_IDENT(X, index, sort, ident) "\"" #ident "\", "
|
||||
|
||||
#define EVENT_PARAM_PREPARE_PLACE(X, index, sort, ident) "?, "
|
||||
|
||||
#define EVENT_TYPE_PREPARE_STATEMENT(X, name, code, always, kind) \
|
||||
stmt_##name = \
|
||||
prepareStatement(db, \
|
||||
"INSERT INTO EVENT_" #name " (" \
|
||||
EVENT_##name##_PARAMS(EVENT_PARAM_PREPARE_IDENT, X) \
|
||||
"log_serial, time) VALUES (" \
|
||||
EVENT_##name##_PARAMS(EVENT_PARAM_PREPARE_PLACE,X) \
|
||||
"?, ?)");
|
||||
|
||||
#define EVENT_TYPE_FINALIZE_STATEMENT(X, name, code, always, kind) \
|
||||
finalizeStatement(db, stmt_##name);
|
||||
|
||||
#define EVENT_PARAM_BIND_A bind_int
|
||||
#define EVENT_PARAM_BIND_P bind_int
|
||||
#define EVENT_PARAM_BIND_U bind_int
|
||||
#define EVENT_PARAM_BIND_W bind_int
|
||||
#define EVENT_PARAM_BIND_D bind_real
|
||||
#define EVENT_PARAM_BIND_S bind_text
|
||||
#define EVENT_PARAM_BIND_B bind_int
|
||||
|
||||
#define EVENT_PARAM_BIND(X, index, sort, ident) \
|
||||
p = EVENT_PARAM_BIND_##sort (db, statement, eventCount, index+1, p); \
|
||||
last_index = index+1;
|
||||
|
||||
#define EVENT_TYPE_WRITE_SQL(X, name, code, always, kind) \
|
||||
case code: \
|
||||
statement = stmt_##name; \
|
||||
/* bind all the parameters of this particular event with macro magic. */ \
|
||||
EVENT_##name##_PARAMS(EVENT_PARAM_BIND, X) \
|
||||
break;
|
||||
|
||||
static char *bind_int(sqlite3 *db, sqlite3_stmt *stmt, int64 count, int index, char *p)
|
||||
{
|
||||
char *q;
|
||||
long long val;
|
||||
int res;
|
||||
|
||||
while(*p == ' ')
|
||||
++p;
|
||||
|
||||
val = strtoll(p, &q, 16);
|
||||
if (q == p)
|
||||
error("event %llu field %d not an integer: %s",
|
||||
count, index, p);
|
||||
|
||||
res = sqlite3_bind_int64(stmt, index, val);
|
||||
if (res != SQLITE_OK)
|
||||
sqlite_error(res, db, "event %llu field %d bind failed", count, index);
|
||||
return q;
|
||||
}
|
||||
|
||||
static char *bind_real(sqlite3 *db, sqlite3_stmt *stmt, int64 count, int index, char *p)
|
||||
{
|
||||
char *q;
|
||||
double val;
|
||||
int res;
|
||||
|
||||
while(*p == ' ')
|
||||
++p;
|
||||
|
||||
val = strtod(p, &q);
|
||||
if (q == p)
|
||||
error("event %llu field %d not a floating-point value: %s",
|
||||
count, index, p);
|
||||
|
||||
res = sqlite3_bind_double(stmt, index, val);
|
||||
if (res != SQLITE_OK)
|
||||
sqlite_error(res, db, "event %llu field %d bind failed", count, index);
|
||||
return q;
|
||||
}
|
||||
|
||||
static char *bind_text(sqlite3 *db, sqlite3_stmt *stmt, int64 count, int index, char *p)
|
||||
{
|
||||
char *q;
|
||||
int res;
|
||||
|
||||
while(*p == ' ')
|
||||
++p;
|
||||
|
||||
q = p;
|
||||
while((*q != '\n') && (*q != '\0')) {
|
||||
++ q;
|
||||
}
|
||||
if ((q == p) || (q[-1] != '"'))
|
||||
error("event %llu string field %d has no closing quote mark.",
|
||||
count, index);
|
||||
|
||||
res = sqlite3_bind_text(stmt, index, p, (int)(q-p-1), SQLITE_STATIC);
|
||||
if (res != SQLITE_OK)
|
||||
sqlite_error(res, db, "event %llu field %d bind failed", count, index);
|
||||
return q;
|
||||
}
|
||||
|
||||
/* readLog -- read and parse log. Returns the number of events written.
|
||||
*/
|
||||
|
||||
static int64 readLog(FILE *input,
|
||||
sqlite3 *db)
|
||||
{
|
||||
int64 eventCount = 0;
|
||||
|
||||
/* declare statements for every event type */
|
||||
EVENT_LIST(EVENT_TYPE_DECLARE_STATEMENT, X);
|
||||
|
||||
/* prepare statements for every event type */
|
||||
EVENT_LIST(EVENT_TYPE_PREPARE_STATEMENT, X);
|
||||
|
||||
runStatement(db, "BEGIN", "Transaction start");
|
||||
|
||||
while (TRUE) { /* loop for each event */
|
||||
char line[1024];
|
||||
char *p;
|
||||
char *q;
|
||||
int last_index=0;
|
||||
sqlite3_stmt *statement;
|
||||
int res;
|
||||
int64 clock;
|
||||
long code;
|
||||
|
||||
p = fgets(line, 1024, input);
|
||||
if (!p) {
|
||||
if (feof(input))
|
||||
break;
|
||||
else
|
||||
error("Couldn't read line after event %llu", eventCount);
|
||||
}
|
||||
|
||||
eventCount++;
|
||||
|
||||
clock = strtoll(p, &q, 16);
|
||||
if (q == p)
|
||||
error("event %llu clock field not a hex integer: %s",
|
||||
eventCount, p);
|
||||
|
||||
if (*q != ' ')
|
||||
error("event %llu code field not preceded by ' ': %s",
|
||||
eventCount, q);
|
||||
while(*q == ' ')
|
||||
++q;
|
||||
|
||||
p = q;
|
||||
code = strtol(p, &q, 16);
|
||||
if (q == p)
|
||||
error("event %llu code field %d not an integer: %s",
|
||||
eventCount, index, p);
|
||||
p = q;
|
||||
/* Write event to SQLite. */
|
||||
switch (code) {
|
||||
/* this macro sets statement and last_index */
|
||||
EVENT_LIST(EVENT_TYPE_WRITE_SQL, X);
|
||||
default:
|
||||
error("Event %llu has Unknown event code %d", eventCount, code);
|
||||
}
|
||||
/* bind the fields we store for every event */ \
|
||||
res = sqlite3_bind_int64(statement, last_index+1, logSerial);
|
||||
if (res != SQLITE_OK)
|
||||
sqlite_error(res, db, "Event %llu bind of log_serial failed.", eventCount);
|
||||
res = sqlite3_bind_int64(statement, last_index+2, clock);
|
||||
if (res != SQLITE_OK)
|
||||
sqlite_error(res, db, "Event %llu bind of clock failed.", eventCount);
|
||||
res = sqlite3_step(statement);
|
||||
if (res != SQLITE_DONE)
|
||||
sqlite_error(res, db, "insert of event %llu failed.", eventCount);
|
||||
res = sqlite3_reset(statement);
|
||||
if (res != SQLITE_OK)
|
||||
sqlite_error(res, db, "Couldn't reset insert statement of event %llu", eventCount);
|
||||
|
||||
if (verbosity > LOG_ALWAYS) {
|
||||
if ((eventCount % SMALL_TICK) == 0) {
|
||||
printf(".");
|
||||
fflush(stdout);
|
||||
if (((eventCount / SMALL_TICK) % BIG_TICK) == 0) {
|
||||
printf("\n");
|
||||
fflush(stdout);
|
||||
log(LOG_SOMETIMES, "%lu events.", (unsigned long)eventCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (verbosity > LOG_ALWAYS) {
|
||||
printf("\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
runStatement(db, "COMMIT", "Transaction finish");
|
||||
logFileCompleted(db, eventCount);
|
||||
|
||||
/* finalize all the statements */
|
||||
EVENT_LIST(EVENT_TYPE_FINALIZE_STATEMENT, X);
|
||||
|
||||
return eventCount;
|
||||
}
|
||||
|
||||
static FILE *openLog(sqlite3 *db)
|
||||
{
|
||||
FILE *input;
|
||||
|
||||
registerLogFile(db, logFileName);
|
||||
if (!logFileName) {
|
||||
input = stdin;
|
||||
logFileName = "<stdin>";
|
||||
} else {
|
||||
input = fopen(logFileName, "r");
|
||||
if (input == NULL)
|
||||
error("unable to open %s", logFileName);
|
||||
}
|
||||
|
||||
log(LOG_OFTEN, "Reading %s.", logFileName ? logFileName : "standard input");
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
static int64 writeEventsToSQL(sqlite3 *db)
|
||||
{
|
||||
FILE *input;
|
||||
int64 count;
|
||||
input = openLog(db);
|
||||
count = readLog(input, db);
|
||||
(void)fclose(input);
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
sqlite3 *db;
|
||||
int64 count;
|
||||
|
||||
parseArgs(argc, argv);
|
||||
|
||||
db = openDatabase();
|
||||
if (rebuild) {
|
||||
dropGlueTables(db);
|
||||
}
|
||||
makeTables(db);
|
||||
fillGlueTables(db);
|
||||
count = writeEventsToSQL(db);
|
||||
log(LOG_ALWAYS, "Imported %llu events from %s to %s, serial %lu.",
|
||||
count, logFileName, databaseName, logSerial);
|
||||
|
||||
if (runTests) {
|
||||
/* TODO: more unit tests in here */
|
||||
testTableExists(db);
|
||||
}
|
||||
|
||||
closeDatabase(db);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* COPYRIGHT AND LICENSE
|
||||
*
|
||||
* Copyright (C) 2012 Ravenbrook Limited <http://www.ravenbrook.com/>.
|
||||
* All rights reserved. This is an open source license. Contact
|
||||
* Ravenbrook for commercial licensing options.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Redistributions in any form must be accompanied by information on how
|
||||
* to obtain complete source code for this software and any accompanying
|
||||
* software that uses this software. The source code must either be
|
||||
* included in the distribution or be available for no more than the cost
|
||||
* of distribution plus a nominal fee, and must be freely redistributable
|
||||
* under reasonable conditions. For an executable file, complete source
|
||||
* code means the source code for all modules it contains. It does not
|
||||
* include source code for modules or files that typically accompany the
|
||||
* major components of the operating system on which the executable file
|
||||
* runs.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
* PURPOSE, OR NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
|
@ -13,6 +13,7 @@
|
|||
buildPhases = (
|
||||
);
|
||||
dependencies = (
|
||||
2D07B9791636FCBD00DB751B /* PBXTargetDependency */,
|
||||
3104AFF6156D37BC000A585A /* PBXTargetDependency */,
|
||||
3114A6D5156E9839001E0AA3 /* PBXTargetDependency */,
|
||||
3114A6B9156E9763001E0AA3 /* PBXTargetDependency */,
|
||||
|
|
@ -51,6 +52,8 @@
|
|||
/* End PBXAggregateTarget section */
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
2D07B97A1636FCCE00DB751B /* eventsql.c in Sources */ = {isa = PBXBuildFile; fileRef = 2D07B96C1636FC7200DB751B /* eventsql.c */; };
|
||||
2D07B97C163705E400DB751B /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D07B97B163705E400DB751B /* libsqlite3.dylib */; };
|
||||
3104AFBF156D3591000A585A /* apss.c in Sources */ = {isa = PBXBuildFile; fileRef = 3104AFBE156D3591000A585A /* apss.c */; };
|
||||
3104AFC2156D35B2000A585A /* libmps.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 31EEABFB156AAF9D00714D05 /* libmps.a */; };
|
||||
3104AFC3156D35C3000A585A /* testlib.c in Sources */ = {isa = PBXBuildFile; fileRef = 31EEAC9E156AB73400714D05 /* testlib.c */; };
|
||||
|
|
@ -196,6 +199,13 @@
|
|||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
2D07B9781636FCBD00DB751B /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 31EEABDA156AAE9E00714D05 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 2D07B9701636FC9900DB751B;
|
||||
remoteInfo = eventsql;
|
||||
};
|
||||
3104AFC0156D35AE000A585A /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 31EEABDA156AAE9E00714D05 /* Project object */;
|
||||
|
|
@ -626,6 +636,15 @@
|
|||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
2D07B96F1636FC9900DB751B /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = /usr/share/man/man1/;
|
||||
dstSubfolderSpec = 0;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 1;
|
||||
};
|
||||
3104AFB1156D357B000A585A /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
|
@ -899,6 +918,9 @@
|
|||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
2D07B96C1636FC7200DB751B /* eventsql.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = eventsql.c; sourceTree = "<group>"; };
|
||||
2D07B9711636FC9900DB751B /* eventsql */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = eventsql; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
2D07B97B163705E400DB751B /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = usr/lib/libsqlite3.dylib; sourceTree = SDKROOT; };
|
||||
3104AFA5156D27E7000A585A /* ssixi6.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ssixi6.c; sourceTree = "<group>"; };
|
||||
3104AFB3156D357B000A585A /* apss */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = apss; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3104AFBE156D3591000A585A /* apss.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = apss.c; sourceTree = "<group>"; };
|
||||
|
|
@ -1026,6 +1048,14 @@
|
|||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
2D07B96E1636FC9900DB751B /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
2D07B97C163705E400DB751B /* libsqlite3.dylib in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
3104AFB0156D357B000A585A /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
|
@ -1276,6 +1306,21 @@
|
|||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
2D07B96A1636FC4C00DB751B /* eventsql */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2D07B96C1636FC7200DB751B /* eventsql.c */,
|
||||
);
|
||||
name = eventsql;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
2D07B9731636FC9900DB751B /* eventsql */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
path = eventsql;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3114A647156E956C001E0AA3 /* Mysterious */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -1288,6 +1333,7 @@
|
|||
3114A6D6156E9846001E0AA3 /* Tools */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2D07B96A1636FC4C00DB751B /* eventsql */,
|
||||
3114A6D8156E9942001E0AA3 /* eventcnv */,
|
||||
);
|
||||
name = Tools;
|
||||
|
|
@ -1374,10 +1420,12 @@
|
|||
31EEABD8156AAE9E00714D05 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2D07B97B163705E400DB751B /* libsqlite3.dylib */,
|
||||
3114A6D6156E9846001E0AA3 /* Tools */,
|
||||
3114A647156E956C001E0AA3 /* Mysterious */,
|
||||
31A47BA8156C1E930039B1C2 /* MPS */,
|
||||
3124CAB3156BE1B700753214 /* Tests */,
|
||||
2D07B9731636FC9900DB751B /* eventsql */,
|
||||
31EEABEF156AAF5C00714D05 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -1416,6 +1464,7 @@
|
|||
3114A695156E971B001E0AA3 /* messtest */,
|
||||
3114A6AC156E9759001E0AA3 /* walkt0 */,
|
||||
3114A6C6156E9815001E0AA3 /* eventcnv */,
|
||||
2D07B9711636FC9900DB751B /* eventsql */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -1515,6 +1564,23 @@
|
|||
/* End PBXHeadersBuildPhase section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
2D07B9701636FC9900DB751B /* eventsql */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 2D07B9741636FC9900DB751B /* Build configuration list for PBXNativeTarget "eventsql" */;
|
||||
buildPhases = (
|
||||
2D07B96D1636FC9900DB751B /* Sources */,
|
||||
2D07B96E1636FC9900DB751B /* Frameworks */,
|
||||
2D07B96F1636FC9900DB751B /* CopyFiles */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = eventsql;
|
||||
productName = eventsql;
|
||||
productReference = 2D07B9711636FC9900DB751B /* eventsql */;
|
||||
productType = "com.apple.product-type.tool";
|
||||
};
|
||||
3104AFB2156D357B000A585A /* apss */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 3104AFBC156D357B000A585A /* Build configuration list for PBXNativeTarget "apss" */;
|
||||
|
|
@ -2124,11 +2190,20 @@
|
|||
3114A694156E971B001E0AA3 /* messtest */,
|
||||
3114A6AB156E9759001E0AA3 /* walkt0 */,
|
||||
3114A6C5156E9815001E0AA3 /* eventcnv */,
|
||||
2D07B9701636FC9900DB751B /* eventsql */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
2D07B96D1636FC9900DB751B /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
2D07B97A1636FCCE00DB751B /* eventsql.c in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
3104AFAF156D357B000A585A /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
|
@ -2461,6 +2536,11 @@
|
|||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
2D07B9791636FCBD00DB751B /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 2D07B9701636FC9900DB751B /* eventsql */;
|
||||
targetProxy = 2D07B9781636FCBD00DB751B /* PBXContainerItemProxy */;
|
||||
};
|
||||
3104AFC1156D35AE000A585A /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 31EEABFA156AAF9D00714D05 /* mps */;
|
||||
|
|
@ -2769,6 +2849,30 @@
|
|||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
2D07B9751636FC9900DB751B /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
GCC_C_LANGUAGE_STANDARD = c99;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
2D07B9761636FC9900DB751B /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
GCC_C_LANGUAGE_STANDARD = c99;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
2D07B9771636FC9900DB751B /* WE */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
GCC_C_LANGUAGE_STANDARD = c99;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = WE;
|
||||
};
|
||||
3104AFBA156D357B000A585A /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
|
|
@ -3616,6 +3720,15 @@
|
|||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
2D07B9741636FC9900DB751B /* Build configuration list for PBXNativeTarget "eventsql" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
2D07B9751636FC9900DB751B /* Debug */,
|
||||
2D07B9761636FC9900DB751B /* Release */,
|
||||
2D07B9771636FC9900DB751B /* WE */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
};
|
||||
3104AFBC156D357B000A585A /* Build configuration list for PBXNativeTarget "apss" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
|
||||
int mps_lib_get_EOF(void)
|
||||
|
|
@ -119,7 +120,9 @@ unsigned long mps_lib_telemetry_control(void)
|
|||
unsigned long mask;
|
||||
char buf[256];
|
||||
char *word;
|
||||
char *p;
|
||||
char *sep = " ";
|
||||
char rowName[256];
|
||||
|
||||
s = getenv("MPS_TELEMETRY_CONTROL");
|
||||
if (s == NULL)
|
||||
|
|
@ -130,14 +133,29 @@ unsigned long mps_lib_telemetry_control(void)
|
|||
if (mask != 0)
|
||||
return mask;
|
||||
|
||||
/* Split the value at spaces and try to patch the words against the names
|
||||
of event kinds, enabling them if there's a match. */
|
||||
/* copy the envar to a buffer so we can mess with it. */
|
||||
strncpy(buf, s, sizeof(buf) - 1);
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
/* downcase it */
|
||||
for (p = buf; *p != '\0'; ++p)
|
||||
*p = (char)tolower(*p);
|
||||
|
||||
/* Split the value at spaces and try to match the words against the names
|
||||
of event kinds, enabling them if there's a match. */
|
||||
for (word = strtok(buf, sep); word != NULL; word = strtok(NULL, sep)) {
|
||||
#define TELEMATCH(X, rowName, rowDoc) \
|
||||
if (strcmp(word, #rowName) == 0) \
|
||||
mask |= (1ul << EventKind##rowName);
|
||||
if (strcmp(word, "all") == 0) {
|
||||
mask = (unsigned long)-1;
|
||||
printf("All events.");
|
||||
return mask;
|
||||
}
|
||||
#define TELEMATCH(X, name, rowDoc) \
|
||||
strncpy(rowName, #name, sizeof(rowName) - 1); \
|
||||
rowName[sizeof(rowName) - 1] = '\0'; \
|
||||
for (p = rowName; *p != '\0'; ++p) \
|
||||
*p = (char)tolower(*p); \
|
||||
if (strcmp(word, rowName) == 0) { \
|
||||
mask |= (1ul << EventKind##name); \
|
||||
printf("Events to include " rowDoc "\n"); }
|
||||
EventKindENUM(TELEMATCH, X)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -188,96 +188,7 @@ since you know that the frame is referenced by the ``car`` of the
|
|||
first pair in the environment, that's the suspect reference. But in a
|
||||
more complex situation this might not yet be clear. In such a
|
||||
situation it can be useful to look at the sequence of events leading
|
||||
up to the detection of the error, and in order to enable you to do
|
||||
that, the MPS provides its :ref:`topic-telemetry` feature.
|
||||
|
||||
|
||||
.. _guide-debug-telemetry:
|
||||
|
||||
Telemetry
|
||||
---------
|
||||
|
||||
In its :term:`cool` :term:`variety`, the MPS is capable of outputting
|
||||
a configurable stream of events to assist with debugging and
|
||||
profiling. The exact mechanism by which the telemetry stream is
|
||||
controlled is in theory configurable via the :ref:`topic-plinth`, but
|
||||
if you haven't done so then you can set the environment variables
|
||||
:envvar:`MPS_TELEMETRY_CONTROL` and :envvar:`MPS_TELEMETRY_FILENAME`.
|
||||
Setting the former to "``65535``" turns on all events, and the default
|
||||
value for the latter is "``mpsio.log``" which is fine. So let's run
|
||||
the test case with telemetry turned on::
|
||||
|
||||
$ gdb ./scheme
|
||||
GNU gdb 6.3.50-20050815 (Apple version gdb-1820) (Sat Jun 16 02:40:11 UTC 2012)
|
||||
[...]
|
||||
(gdb) set environment MPS_TELEMETRY_CONTROL=65535
|
||||
(gdb) run
|
||||
Starting program: example/scheme/scheme
|
||||
Reading symbols for shared libraries +............................. done
|
||||
MPS Toy Scheme Example
|
||||
[...]
|
||||
7944, 0> (gc)
|
||||
[...]
|
||||
7968, 1> foo
|
||||
Assertion failed: (TYPE(frame) == TYPE_PAIR), function lookup_in_frame, file scheme.c, line 1066.
|
||||
|
||||
Program received signal SIGABRT, Aborted.
|
||||
0x00007fff91aeed46 in __kill ()
|
||||
|
||||
At this point there's still output in the MPS's internal event
|
||||
buffers, which needs to be flushed. It would be a good idea to add a
|
||||
call to :c:func:`mps_telemetry_flush` to the error handler, but for
|
||||
now we can just call it directly from the debugger::
|
||||
|
||||
(gdb) print mps_telemetry_flush()
|
||||
$1 = void
|
||||
|
||||
The MPS writes the telemetry to the log in an encoded form for speed.
|
||||
It can be decoded using the :ref:`eventcnv <telemetry-eventcnv>`
|
||||
program::
|
||||
|
||||
(gdb) shell eventcnv | sort > mpsio.txt
|
||||
|
||||
The ``sort`` is useful because the events are not necessarily written
|
||||
to the telemetry file in time order, but each event starts with a
|
||||
timestamp so sorting makes a time series. The decoded events look like
|
||||
this, with the timestamp in the first column (in units of
|
||||
:c:type:`mps_clock_t`, typically 1 µs), the event type in the second
|
||||
column, and then addresses or other data related to the event in the
|
||||
remaining columns. All numbers are given in hexadecimal. ::
|
||||
|
||||
00000C0DC0DA69DD395 VMCreate 100128000 100129000 10012A000
|
||||
00000C0DC0DA69E1ACF VMMap 100128000 100129000 10012A000
|
||||
00000C0DC0DA69FF118 Intern 2 Reservoir
|
||||
00000C0DC0DA69FF358 Label 1000BD4A8 sym 00002
|
||||
00000C0DC0DA6A04890 PoolInit 100129328 100129000 1000BD4A8
|
||||
00000C0DC0DA6A0C813 VMCreate 10012A000 100300000 100400000
|
||||
00000C0DC0DA6A120B4 VMMap 10012A000 100300000 100301000
|
||||
00000C0DC0DA6A1D0F1 ArenaCreateVM 100129000 100000 100000
|
||||
|
||||
You can search through the telemetry for events related to particular
|
||||
addresses of interest. Here we look for events related to the address
|
||||
of the corrupted ``frame`` object::
|
||||
|
||||
(gdb) frame 3
|
||||
#3 0x0000000100003f55 in lookup_in_frame (frame=0x1003fa7d0, symbol=0x1003faf20) at scheme.c:1066
|
||||
1066 assert(TYPE(frame) == TYPE_PAIR);
|
||||
(gdb) print frame
|
||||
$2 = (obj_t) 0x1003fa7d0
|
||||
(gdb) shell grep -i 1003fa7d0 mpsio.txt || echo not found
|
||||
not found
|
||||
|
||||
There are no events related to this address, so in particular this
|
||||
address was never fixed (no ``TraceFix`` event). This should be enough
|
||||
of a clue to track down the cause.
|
||||
|
||||
.. note::
|
||||
|
||||
You may find it useful to add the command::
|
||||
|
||||
set environment MPS_TELEMETRY_CONTROL=65535
|
||||
|
||||
to your ``.gdbinit``.
|
||||
up to the detection of the error. See :ref:`topic-telemetry`.
|
||||
|
||||
|
||||
.. _guide-debug-size:
|
||||
|
|
|
|||
|
|
@ -31,12 +31,97 @@ John McCarthy described the first on-line demonstration of
|
|||
thought we were victims of a practical joker.
|
||||
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
Typical uses of telemetry labels include:
|
||||
In its :term:`cool` :term:`variety`, the MPS is capable of outputting
|
||||
a configurable stream of events to assist with debugging and
|
||||
profiling.
|
||||
|
||||
- Label pools with a human-meaningful name;
|
||||
The selection of events that appear in the stream is controlled by the
|
||||
environment variable :envvar:`MPS_TELEMETRY_CONTROL` (by default
|
||||
none), and the stream is written to the file named by the environment
|
||||
variable :envvar:`MPS_TELEMETRY_FILENAME` (by default ``mpsio.log``).
|
||||
|
||||
- Label allocated objects with their type or class.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
Here's an example of turning on telemetry in the debugger and then
|
||||
encountering a corrupted object::
|
||||
|
||||
$ gdb ./scheme
|
||||
GNU gdb 6.3.50-20050815 (Apple version gdb-1820) (Sat Jun 16 02:40:11 UTC 2012)
|
||||
[...]
|
||||
(gdb) set environment MPS_TELEMETRY_CONTROL=all
|
||||
(gdb) run
|
||||
Starting program: example/scheme/scheme
|
||||
Reading symbols for shared libraries +............................. done
|
||||
MPS Toy Scheme Example
|
||||
[...]
|
||||
7944, 0> (gc)
|
||||
[...]
|
||||
7968, 1> foo
|
||||
Assertion failed: (TYPE(frame) == TYPE_PAIR), function lookup_in_frame, file scheme.c, line 1066.
|
||||
|
||||
Program received signal SIGABRT, Aborted.
|
||||
0x00007fff91aeed46 in __kill ()
|
||||
|
||||
At this point there's still output in the MPS's internal event
|
||||
buffers, which needs to be flushed. It would be a good idea to add a
|
||||
call to :c:func:`mps_telemetry_flush` to the error handler, but for
|
||||
now we can just call it directly from the debugger::
|
||||
|
||||
(gdb) print mps_telemetry_flush()
|
||||
$1 = void
|
||||
|
||||
The MPS writes the telemetry to the log in an encoded form for speed.
|
||||
It can be decoded using the :ref:`eventcnv <telemetry-eventcnv>`
|
||||
program::
|
||||
|
||||
(gdb) shell eventcnv | sort > mpsio.txt
|
||||
|
||||
The ``sort`` is useful because the events are not necessarily written
|
||||
to the telemetry file in time order, but each event starts with a
|
||||
timestamp so sorting makes a time series. The decoded events look like
|
||||
this, with the timestamp in the first column (in units of
|
||||
:c:type:`mps_clock_t`, typically 1 µs), the event type in the second
|
||||
column, and then addresses or other data related to the event in the
|
||||
remaining columns. All numbers are given in hexadecimal. ::
|
||||
|
||||
000AE03973336E3C 2B 1003FC000 1003FD000 1003FE000
|
||||
000AE0397333BC6D 2D 1003FC000 1003FD000 1003FE000
|
||||
000AE0397334DF9F 1A 2 "Reservoir"
|
||||
000AE0397334E0A0 1B 1078C85B8 2
|
||||
000AE03973352375 15 1003FD328 1003FD000 1078C85B8
|
||||
000AE039733592F9 2B 1003FE000 1003FF000 10992F000
|
||||
000AE0397335C8B5 2D 1003FE000 1003FF000 107930000
|
||||
000AE03973361D5A 5 1003FD000 2000000 2000000
|
||||
|
||||
You can search through the telemetry for events related to particular
|
||||
addresses of interest.
|
||||
|
||||
In the example, we might look for events related to the address of the
|
||||
corrupted ``frame`` object::
|
||||
|
||||
(gdb) frame 3
|
||||
#3 0x0000000100003f55 in lookup_in_frame (frame=0x1003fa7d0, symbol=0x1003faf20) at scheme.c:1066
|
||||
1066 assert(TYPE(frame) == TYPE_PAIR);
|
||||
(gdb) print frame
|
||||
$2 = (obj_t) 0x1003fa7d0
|
||||
(gdb) shell grep -i 1003fa7d0 mpsio.txt || echo not found
|
||||
not found
|
||||
|
||||
There are no events related to this address, so in particular this
|
||||
address was never fixed (no ``TraceFix`` event).
|
||||
|
||||
.. note::
|
||||
|
||||
You may find it useful to add the command::
|
||||
|
||||
set environment MPS_TELEMETRY_CONTROL=all
|
||||
|
||||
to your ``.gdbinit``.
|
||||
|
||||
|
||||
Event categories
|
||||
|
|
@ -73,14 +158,16 @@ telemetry feature.
|
|||
|
||||
If its value can be interpreted as a number, then this number
|
||||
represents the set of event categories as a :term:`bitmap`. For
|
||||
example, this turns on event categories numbered 0 to 15::
|
||||
example, this turns on the ``Pool`` and ``Seg`` event categories::
|
||||
|
||||
MPS_TELEMETRY_CONTROL=65535
|
||||
MPS_TELEMETRY_CONTROL=6
|
||||
|
||||
Otherwise, the value is split into words at spaces, and any word
|
||||
that names an event category turns it on. For example::
|
||||
|
||||
MPS_TELEMETRY_CONTROL="Arena Pool Trace"
|
||||
MPS_TELEMETRY_CONTROL="arena pool trace"
|
||||
|
||||
The special event category ``all`` turns on all events.
|
||||
|
||||
.. note::
|
||||
|
||||
|
|
@ -111,26 +198,6 @@ standard output) a representation of each event in the stream.
|
|||
|
||||
The name of the file containing the telemetry stream to decode.
|
||||
Defaults to ``mpsio.log``.
|
||||
|
||||
.. option:: -S
|
||||
|
||||
Format output human-readably. This is the default output style.
|
||||
For example::
|
||||
|
||||
000007DC7DC1655516E TraceFix 7FFF583001D0 7FFF583000D8 107AFAB20 1
|
||||
|
||||
.. option:: -SL
|
||||
|
||||
Format output as S-expressions for consumption by :term:`Lisp`.
|
||||
For example::
|
||||
|
||||
(000007DC7DC1655516E TraceFix 7FFF583001D0 7FFF583000D8 107AFAB20 1)
|
||||
|
||||
.. option:: -SC
|
||||
|
||||
Format output as CSV (comma-separated values). For example::
|
||||
|
||||
000007DC7DC1655516E, 38, 140734672929232, 140734672928984, 4423920416, 1
|
||||
|
||||
.. option:: -h
|
||||
|
||||
|
|
@ -193,6 +260,16 @@ Telemetry interface
|
|||
even try calling this from a debugger after a problem.
|
||||
|
||||
|
||||
Telemetry labels
|
||||
----------------
|
||||
|
||||
Typical uses of telemetry labels include:
|
||||
|
||||
- Label pools with a human-meaningful name;
|
||||
|
||||
- Label allocated objects with their type or class.
|
||||
|
||||
|
||||
.. c:function:: mps_word_t mps_telemetry_intern(char *label)
|
||||
|
||||
Registers a string with the MPS, and receives a :term:`telemetry
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue