blob: af2df99d0db842ebcde1eeca3b5b1ece3415eb59 [file] [log] [blame]
/* Copyright 2001-2004 The Apache Software Foundation
*
* Licensed 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.
*
* Originally developed by Aaron Bannert and Justin Erenkrantz, eBuilt.
*/
#include <apr_file_io.h>
#include <apr_strings.h>
#include <apr_errno.h>
#if APR_HAVE_STRING_H
#include <string.h> /* strncasecmp */
#endif
#if APR_HAVE_STDLIB_H
#include <stdlib.h> /* strtol */
#endif
#include "config.h"
#include "flood_profile.h"
#include "flood_config.h"
#include "flood_net.h"
#if FLOOD_HAS_OPENSSL
#include "flood_net_ssl.h"
#endif /* FLOOD_HAS_OPENSSL */
#include "flood_round_robin.h"
#include "flood_simple_reports.h"
#include "flood_easy_reports.h"
#include "flood_socket_generic.h"
#include "flood_socket_keepalive.h"
#include "flood_report_relative_times.h"
extern apr_file_t *local_stdout;
extern apr_file_t *local_stderr;
struct profile_event_handler_t {
const char *handler_name;
const char *impl_name;
void *handler;
};
typedef struct profile_event_handler_t profile_event_handler_t;
struct profile_group_handler_t {
const char *class;
const char *group_name;
const char **handlers;
};
typedef struct profile_group_handler_t profile_group_handler_t;
/**
* Generic implementation for profile_init.
*/
static apr_status_t generic_profile_init(profile_t **profile, config_t *config, const char * profile_name, apr_pool_t *pool)
{
return APR_ENOTIMPL;
}
/**
* Generic implementation for report_init.
*/
static apr_status_t generic_report_init(report_t **report, config_t *config, const char *profile_name, apr_pool_t *pool)
{
/* by default, don't generate a report */
*report = NULL;
return APR_SUCCESS;
}
/**
* Generic implementation for get_next_url.
*/
static apr_status_t generic_get_next_url(request_t **request, profile_t *profile)
{
return APR_ENOTIMPL;
}
/**
* Generic implementation for create_req.
*/
static apr_status_t generic_create_req(profile_t *profile, request_t *request)
{
return APR_ENOTIMPL;
}
/**
* Generic implementation for postprocess.
*/
static apr_status_t generic_postprocess(profile_t *p, request_t *req, response_t *resp)
{
return APR_SUCCESS;
}
/**
* Generic implementation for verify_resp.
*/
static apr_status_t generic_verify_resp(int *verified, profile_t *profile, request_t *req, response_t *resp)
{
return APR_ENOTIMPL;
}
/**
* Generic implementation for process_stats.
*/
static apr_status_t generic_process_stats(report_t *report, int verified, request_t *req, response_t *resp)
{
/* by default report nothing */
return APR_SUCCESS;
}
/**
* Generic implementation for loop_condition.
*/
static int generic_loop_condition(profile_t *p)
{
return 0; /* by default, don't loop */
}
/**
* Generic implementation for request_destroy.
*/
static apr_status_t generic_request_destroy(request_t *req)
{
return APR_SUCCESS;
}
/**
* Generic implementation for response_destroy.
*/
static apr_status_t generic_response_destroy(response_t *resp)
{
return APR_SUCCESS;
}
/**
* Generic implementation for report_stats.
*/
static apr_status_t generic_report_stats(report_t *report)
{
/* nothing to report, but not a failure */
return APR_SUCCESS;
}
/**
* Generic implementation for destroy_report.
*/
static apr_status_t generic_destroy_report(report_t *report)
{
return APR_SUCCESS;
}
/**
* Generic implementation for profile_destroy.
*/
static apr_status_t generic_profile_destroy(profile_t *p)
{
return APR_SUCCESS;
}
const char *profile_event_handler_names[] = {
"profile_init",
"report_init",
"get_next_url",
"socket_init",
"begin_conn",
"create_req",
"send_req",
"recv_resp",
"postprocess",
"verify_resp",
"process_stats",
"loop_condition",
"end_conn",
"request_destroy",
"response_destroy",
"socket_destroy",
"report_stats",
"destroy_report",
"profile_destroy",
NULL
};
profile_event_handler_t profile_event_handlers[] = {
{"profile_init", "generic_profile_init", &generic_profile_init},
{"report_init", "generic_report_init", &generic_report_init},
{"get_next_url", "generic_get_next_url", &generic_get_next_url},
{"socket_init", "generic_socket_init", &generic_socket_init},
{"begin_conn", "generic_begin_conn", &generic_begin_conn},
{"create_req", "generic_create_req", &generic_create_req},
{"send_req", "generic_send_req", &generic_send_req},
{"recv_resp", "generic_recv_resp", &generic_recv_resp},
{"postprocess", "generic_postprocess", &generic_postprocess},
{"verify_resp", "generic_verify_resp", &generic_verify_resp},
{"process_stats", "generic_process_stats", &generic_process_stats},
{"loop_condition", "generic_loop_condition", &generic_loop_condition},
{"end_conn", "generic_end_conn", &generic_end_conn},
{"request_destroy", "generic_request_destroy", &generic_request_destroy},
{"response_destroy", "generic_response_destroy", &generic_response_destroy},
{"socket_destroy", "generic_socket_destroy", &generic_socket_destroy},
{"report_stats", "generic_report_stats", &generic_report_stats},
{"destroy_report", "generic_destroy_report", &generic_destroy_report},
{"profile_destroy", "generic_profile_destroy", &generic_profile_destroy},
/* Alternative Implementations that are currently available: */
/* Always retrieve the full response */
{"recv_resp", "generic_fullresp_recv_resp", &generic_fullresp_recv_resp},
/* Keep-Alive support */
{"socket_init", "keepalive_socket_init", &keepalive_socket_init},
{"begin_conn", "keepalive_begin_conn", &keepalive_begin_conn},
{"send_req", "keepalive_send_req", &keepalive_send_req},
{"recv_resp", "keepalive_recv_resp", &keepalive_recv_resp},
{"end_conn", "keepalive_end_conn", &keepalive_end_conn},
{"socket_destroy", "keepalive_socket_destroy", &keepalive_socket_destroy},
/* Round Robin */
{"profile_init", "round_robin_profile_init", &round_robin_profile_init},
{"get_next_url", "round_robin_get_next_url", &round_robin_get_next_url},
{"create_req", "round_robin_create_req", &round_robin_create_req},
{"postprocess", "round_robin_postprocess", &round_robin_postprocess},
{"loop_condition", "round_robin_loop_condition", &round_robin_loop_condition},
{"profile_destroy", "round_robin_profile_destroy",&round_robin_profile_destroy},
/* Verification by OK/200 */
{"verify_resp", "verify_200", &verify_200},
{"verify_resp", "verify_status_code", &verify_status_code},
/* Simple Reports */
{"report_init", "simple_report_init", &simple_report_init},
{"process_stats", "simple_process_stats", &simple_process_stats},
{"report_stats", "simple_report_stats", &simple_report_stats},
{"destroy_report", "simple_destroy_report", &simple_destroy_report},
/* Easy Reports */
{"report_init", "easy_report_init", &easy_report_init},
{"process_stats", "easy_process_stats", &easy_process_stats},
{"report_stats", "easy_report_stats", &easy_report_stats},
{"destroy_report", "easy_destroy_report", &easy_destroy_report},
/* Relative Times Report */
{"report_init", "relative_times_report_init", &relative_times_report_init},
{"process_stats", "relative_times_process_stats", &relative_times_process_stats},
{"report_stats", "relative_times_report_stats", &relative_times_report_stats},
{"destroy_report", "relative_times_destroy_report",&relative_times_destroy_report},
{NULL} /* sentinel value */
};
const char * profile_group_handler_names[] = {
"report",
"socket",
"profiletype",
NULL
};
const char * report_easy_group[] = { "easy_report_init", "easy_process_stats", "easy_report_stats", "easy_destroy_report", NULL };
const char * report_simple_group[] = { "simple_report_init", "simple_process_stats", "simple_report_stats", "simple_destroy_report", NULL };
const char * socket_generic_group[] = { "generic_socket_init", "generic_begin_conn", "generic_send_req", "generic_recv_resp", "generic_end_conn", "generic_socket_destroy", NULL };
const char * socket_keepalive_group[] = { "keepalive_socket_init", "keepalive_begin_conn", "keepalive_send_req", "keepalive_recv_resp", "keepalive_end_conn", "keepalive_socket_destroy", NULL };
const char * profile_round_robin_group[] = { "round_robin_profile_init", "round_robin_get_next_url", "round_robin_create_req", "round_robin_postprocess", "round_robin_loop_condition", "round_robin_profile_destroy", NULL };
const char * report_relative_times_group[] = { "relative_times_report_init", "relative_times_process_stats", "relative_times_report_stats", "relative_times_destroy_report", NULL };
profile_group_handler_t profile_group_handlers[] = {
{"report", "easy", report_easy_group },
{"report", "simple", report_simple_group },
{"socket", "generic", socket_generic_group },
{"socket", "keepalive", socket_keepalive_group },
{"profiletype", "round_robin", profile_round_robin_group },
{"report", "relative_times", report_relative_times_group },
{NULL}
};
/**
* Assign the appropriate implementation to the profile_events_t handler
* for the given function name and overriden function name.
* Returns APR_SUCCESS if an appropriate handler was found and assigned, or
* returns APR_ENOTIMPL if no match was found.
*/
static apr_status_t assign_profile_event_handler(profile_events_t *events,
const char *handler_name,
const char *impl_name)
{
profile_event_handler_t *p;
for (p = &profile_event_handlers[0]; p && (*p).handler_name; p++) {
/* these are case insensitive (both key and value) for the sake of simplicity */
if (strncasecmp(impl_name, (*p).impl_name, FLOOD_STRLEN_MAX) == 0) {
if (strncasecmp(handler_name, (*p).handler_name, FLOOD_STRLEN_MAX) == 0) {
/* we got a match, assign it */
/* stupid cascading if, no big deal since it only happens at startup */
if (strncasecmp(handler_name, "profile_init", FLOOD_STRLEN_MAX) == 0) {
events->profile_init = (*p).handler;
} else if (strncasecmp(handler_name, "report_init", FLOOD_STRLEN_MAX) == 0){
events->report_init = (*p).handler;
} else if (strncasecmp(handler_name, "socket_init", FLOOD_STRLEN_MAX) == 0){
events->socket_init = (*p).handler;
} else if (strncasecmp(handler_name, "get_next_url", FLOOD_STRLEN_MAX) == 0){
events->get_next_url = (*p).handler;
} else if (strncasecmp(handler_name, "begin_conn", FLOOD_STRLEN_MAX) == 0) {
events->begin_conn = (*p).handler;
} else if (strncasecmp(handler_name, "create_req", FLOOD_STRLEN_MAX) == 0) {
events->create_req = (*p).handler;
} else if (strncasecmp(handler_name, "send_req", FLOOD_STRLEN_MAX) == 0) {
events->send_req = (*p).handler;
} else if (strncasecmp(handler_name, "recv_resp", FLOOD_STRLEN_MAX) == 0) {
events->recv_resp = (*p).handler;
} else if (strncasecmp(handler_name, "postprocess", FLOOD_STRLEN_MAX) == 0) {
events->postprocess = (*p).handler;
} else if (strncasecmp(handler_name, "verify_resp", FLOOD_STRLEN_MAX) == 0) {
events->verify_resp = (*p).handler;
} else if (strncasecmp(handler_name, "process_stats", FLOOD_STRLEN_MAX) == 0) {
events->process_stats = (*p).handler;
} else if (strncasecmp(handler_name, "loop_condition", FLOOD_STRLEN_MAX) == 0) {
events->loop_condition = (*p).handler;
} else if (strncasecmp(handler_name, "end_conn", FLOOD_STRLEN_MAX) == 0) {
events->end_conn = (*p).handler;
} else if (strncasecmp(handler_name, "request_destroy", FLOOD_STRLEN_MAX) == 0) {
events->request_destroy = (*p).handler;
} else if (strncasecmp(handler_name, "response_destroy", FLOOD_STRLEN_MAX) == 0) {
events->response_destroy = (*p).handler;
} else if (strncasecmp(handler_name, "socket_destroy", FLOOD_STRLEN_MAX) == 0) {
events->socket_destroy = (*p).handler;
} else if (strncasecmp(handler_name, "report_stats", FLOOD_STRLEN_MAX) == 0) {
events->report_stats = (*p).handler;
} else if (strncasecmp(handler_name, "destroy_report", FLOOD_STRLEN_MAX) == 0) {
events->destroy_report = (*p).handler;
} else if (strncasecmp(handler_name, "profile_destroy", FLOOD_STRLEN_MAX) == 0) {
events->profile_destroy = (*p).handler;
} else {
/* some internal error, our static structs don't match up */
return APR_EGENERAL;
}
return APR_SUCCESS;
} else {
/* invalid implementation for this handler */
apr_file_printf(local_stderr, "Invalid handler (%s) "
"specified.\n",
handler_name ? handler_name : "NULL");
return APR_ENOTIMPL; /* XXX: There's probably a better return val than this? */
}
}
}
apr_file_printf(local_stderr, "Invalid implementation (%s) for "
"this handler (%s)\n",
impl_name ? impl_name : "NULL",
handler_name ? handler_name : "NULL");
return APR_ENOTIMPL; /* no implementation found */
}
/**
* Assign all functions listed in the group to the profile_events_t handler
* Returns APR_SUCCESS if an appropriate handler was found and assigned, or
* returns APR_NOTFOUND if no match was found.
*/
static apr_status_t assign_profile_group_handler(profile_events_t *events,
const char *class_name,
const char *group_name)
{
profile_event_handler_t *p;
profile_group_handler_t *g;
const char **handlers;
/* Find our group. */
for (g = &profile_group_handlers[0]; g && g->class; g++)
{
if (!strncasecmp(class_name, g->class, FLOOD_STRLEN_MAX) &&
!strncasecmp(group_name, g->group_name, FLOOD_STRLEN_MAX))
break;
}
if (!g->class) {
apr_file_printf(local_stderr, "Invalid class '%s' or groupname '%s'.\n",
class_name, group_name);
return APR_EGENERAL;
}
/* For all of the handlers, set them. */
for (handlers = g->handlers; *handlers; handlers++)
{
for (p = profile_event_handlers; p && p->handler_name; p++) {
if (!strncasecmp(p->impl_name, *handlers, FLOOD_STRLEN_MAX))
assign_profile_event_handler(events, p->handler_name,
p->impl_name);
}
}
return APR_SUCCESS;
}
/**
* Construct a profile_event_handler_t struct, with the "generic"
* implementations in place by default.
*/
static apr_status_t create_profile_events(profile_events_t **events, apr_pool_t *pool)
{
apr_status_t stat;
profile_events_t *new_events;
if ((new_events = apr_pcalloc(pool, sizeof(profile_events_t))) == NULL)
return APR_ENOMEM;
if ((stat = assign_profile_event_handler(new_events,
"profile_init",
"generic_profile_init")) != APR_SUCCESS)
return stat;
if ((stat = assign_profile_event_handler(new_events,
"report_init",
"generic_report_init")) != APR_SUCCESS)
return stat;
if ((stat = assign_profile_event_handler(new_events,
"socket_init",
"generic_socket_init")) != APR_SUCCESS)
return stat;
if ((stat = assign_profile_event_handler(new_events,
"get_next_url",
"generic_get_next_url")) != APR_SUCCESS)
return stat;
if ((stat = assign_profile_event_handler(new_events,
"create_req",
"generic_create_req")) != APR_SUCCESS)
return stat;
if ((stat = assign_profile_event_handler(new_events,
"begin_conn",
"generic_begin_conn")) != APR_SUCCESS)
return stat;
if ((stat = assign_profile_event_handler(new_events,
"send_req",
"generic_send_req")) != APR_SUCCESS)
return stat;
if ((stat = assign_profile_event_handler(new_events,
"recv_resp",
"generic_recv_resp")) != APR_SUCCESS)
return stat;
if ((stat = assign_profile_event_handler(new_events,
"verify_resp",
"generic_verify_resp")) != APR_SUCCESS)
return stat;
if ((stat = assign_profile_event_handler(new_events,
"postprocess",
"generic_postprocess")) != APR_SUCCESS)
return stat;
if ((stat = assign_profile_event_handler(new_events,
"process_stats",
"generic_process_stats")) != APR_SUCCESS)
return stat;
if ((stat = assign_profile_event_handler(new_events,
"loop_condition",
"generic_loop_condition")) != APR_SUCCESS)
return stat;
if ((stat = assign_profile_event_handler(new_events,
"end_conn",
"generic_end_conn")) != APR_SUCCESS)
return stat;
if ((stat = assign_profile_event_handler(new_events,
"request_destroy",
"generic_request_destroy")) != APR_SUCCESS)
return stat;
if ((stat = assign_profile_event_handler(new_events,
"response_destroy",
"generic_response_destroy")) != APR_SUCCESS)
return stat;
if ((stat = assign_profile_event_handler(new_events,
"socket_destroy",
"generic_socket_destroy")) != APR_SUCCESS)
return stat;
if ((stat = assign_profile_event_handler(new_events,
"report_stats",
"generic_report_stats")) != APR_SUCCESS)
return stat;
if ((stat = assign_profile_event_handler(new_events,
"destroy_report",
"generic_destroy_report")) != APR_SUCCESS)
return stat;
if ((stat = assign_profile_event_handler(new_events,
"profile_destroy",
"generic_profile_destroy")) != APR_SUCCESS)
return stat;
*events = new_events;
return APR_SUCCESS;
}
/**
* Initializes the profile_events_t structure according to what we
* find in the given configuration. Dynamically allocated memory
* is pulled from the given pool.
*/
static apr_status_t initialize_events(profile_events_t **events, const char * profile_name, config_t *config, apr_pool_t *pool)
{
apr_status_t stat;
const char **p;
profile_events_t *new_events;
struct apr_xml_elem *root_elem, *profile_elem, *profile_event_elem;
char *xml_profile;
xml_profile = apr_pstrdup(pool, XML_PROFILE);
if ((stat = retrieve_root_xml_elem(&root_elem, config)) != APR_SUCCESS) {
return stat;
}
if ((stat = create_profile_events(&new_events, pool)) != APR_SUCCESS) {
return stat;
}
/* retrieve our profile xml element */
if ((stat = retrieve_xml_elem_with_childmatch(
&profile_elem, root_elem,
xml_profile, "name", profile_name)) != APR_SUCCESS)
return stat;
/* For each event in the profile_events_t struct, allow the config
* parameters to override an implementation.
*/
for (p = &profile_group_handler_names[0]; *p; p++) {
stat = retrieve_xml_elem_child(&profile_event_elem, profile_elem, *p);
/* We found a match */
if (stat == APR_SUCCESS)
{
stat = assign_profile_group_handler(new_events, *p,
profile_event_elem->first_cdata.first->text);
if (stat != APR_SUCCESS)
return stat;
}
}
/* For each event in the profile_events_t struct, allow the config
* parameters to override an implementation.
*/
for (p = &profile_event_handler_names[0]; *p; p++) {
if ((stat = retrieve_xml_elem_child(
&profile_event_elem,
profile_elem,
*p)) == APR_SUCCESS) {
/* search for the implementation in our tables and assign it */
if ((stat = assign_profile_event_handler(
new_events,
*p,
profile_event_elem->first_cdata.first->text)) != APR_SUCCESS) {
#ifdef PROFILE_DEBUG
apr_file_printf(local_stdout, "Profile '%s' failed to override '%s' with '%s'.\n",
profile_name, *p, profile_event_elem->first_cdata.first->text);
#endif /* PROFILE_DEBUG */
return stat;
} else {
#ifdef PROFILE_DEBUG
apr_file_printf(local_stdout, "Profile '%s' overrides '%s' with '%s'.\n",
profile_name, *p, profile_event_elem->first_cdata.first->text);
#endif /* PROFILE_DEBUG */
}
} else {
#ifdef PROFILE_DEBUG
apr_file_printf(local_stdout, "Profile '%s' uses default '%s'.\n",
profile_name, *p);
#endif /* PROFILE_DEBUG */
}
}
*events = new_events;
return APR_SUCCESS;
}
/**
* Essential guts of the main test loop -- a single run of a test profile:
*/
apr_status_t run_profile(apr_pool_t *pool, config_t *config, const char * profile_name)
{
profile_events_t *events;
profile_t *profile;
report_t *report;
request_t *req;
response_t *resp;
socket_t *socket;
flood_timer_t *timer;
apr_status_t stat;
int verified;
/* init to NULL for the sake of our error checking */
events = NULL;
profile = NULL;
req = NULL;
resp = NULL;
socket = NULL;
verified = FLOOD_INVALID;
/* assign the implementations (function pointers) */
if ((stat = initialize_events(&events, profile_name, config, pool)) != APR_SUCCESS) {
return stat;
}
if (events == NULL) {
apr_file_printf(local_stderr, "Error initializing test profile.\n");
return APR_EGENERAL; /* FIXME: What error code to return? */
}
/* initialize this profile */
if ((stat = events->profile_init(&profile, config, profile_name, pool)) != APR_SUCCESS)
return stat;
/* initialize this report */
if ((stat = events->report_init(&report, config, profile_name, pool)) != APR_SUCCESS)
return stat;
/* initialize the socket */
if ((stat = events->socket_init(&socket, pool)) != APR_SUCCESS)
return stat;
timer = apr_palloc(pool, sizeof(flood_timer_t));
do {
if ((stat = events->get_next_url(&req, profile)) != APR_SUCCESS)
return stat;
/* sample timer "begin" */
timer->begin = apr_time_now();
if ((stat = events->begin_conn(socket, req, pool)) != APR_SUCCESS) {
apr_file_printf(local_stderr, "open request failed (%s).\n",
req->uri);
return stat;
}
/* connect()ion was just made, sample it */
timer->connect = apr_time_now();
/* FIXME: I don't like doing this after we've opened the socket.
* But, I'm not sure how to do it otherwise.
*/
if ((stat = events->create_req(profile, req)) != APR_SUCCESS) {
apr_file_printf(local_stderr, "create request failed (%s).\n",
req->uri);
return stat;
}
/* If we wanted to keep track of our request generation overhead,
* we could take a timer sample here */
if ((stat = events->send_req(socket, req, pool)) != APR_SUCCESS) {
apr_file_printf(local_stderr, "send request failed (%s).\n",
req->uri);
return stat;
}
/* record the time at which we finished sending the entire request */
timer->write = apr_time_now();
if ((stat = events->recv_resp(&resp, socket, pool)) != APR_SUCCESS) {
apr_file_printf(local_stderr, "receive request failed (%s).\n",
req->uri);
return stat;
}
/* record the time at which we received the first chunk of response data */
timer->read = apr_time_now();
if ((stat = events->postprocess(profile, req, resp)) != APR_SUCCESS) {
apr_file_printf(local_stderr, "postprocessing failed (%s).\n",
req->uri);
return stat;
}
if ((stat = events->verify_resp(&verified, profile, req, resp)) != APR_SUCCESS) {
apr_file_printf(local_stderr,
"Error while verifying query (%s).\n", req->uri);
return stat;
}
if ((stat = events->end_conn(socket, req, resp)) != APR_SUCCESS) {
apr_file_printf(local_stderr,
"Unable to end the connection (%s).\n", req->uri);
return stat;
}
/* record the time at which we had finished reading the entire response.
* Note: this sample includes overhead from postprocessing and verification
* and is not a good representation of raw server response speed. */
timer->close = apr_time_now();
if ((stat = events->process_stats(report, verified, req, resp, timer)) != APR_SUCCESS) {
apr_file_printf(local_stderr,
"Unable to process statistics (%s).\n", req->uri);
return stat;
}
if ((stat = events->request_destroy(req)) != APR_SUCCESS) {
apr_file_printf(local_stderr, "Error cleaning up request.\n");
return stat;
}
if ((stat = events->response_destroy(resp)) != APR_SUCCESS) {
apr_file_printf(local_stderr, "Error cleaning up Response.\n");
return stat;
}
if ((stat = events->socket_destroy(socket)) != APR_SUCCESS) {
apr_file_printf(local_stderr, "Error cleaning up Socket.\n");
return stat;
}
} while (events->loop_condition(profile));
if ((stat = events->report_stats(report)) != APR_SUCCESS) {
apr_file_printf(local_stderr, "Unable to report statistics.\n");
return stat;
}
if (events->destroy_report(report) != APR_SUCCESS) {
apr_file_printf(local_stderr, "Error cleaning up report '%s'.\n", profile_name);
return APR_EGENERAL; /* FIXME: What error code to return? */
}
if (events->profile_destroy(profile) != APR_SUCCESS) {
apr_file_printf(local_stderr, "Error cleaning up profile '%s'.\n", profile_name);
return APR_EGENERAL; /* FIXME: What error code to return? */
}
return APR_SUCCESS;
}