flood_profile.c (522 lines of code) (raw):

/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * 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; }