Skip to content
Snippets Groups Projects
Commit 832dac53 authored by Don's avatar Don
Browse files

Initial commit

parents
No related branches found
No related tags found
No related merge requests found
drupal_extension
Test setup information:
Two 2.6ghz dual core 2GB RAM Ubuntu servers on a local LAN (wired to 100MB ports on a linksys switch).
One server serving the Drupal database - MySQL 5.1 with Innodb.
-- Database lived in memory (tmpfs).
One server serving Drupal - Nginx with fast CGI.
-- Drupal lived in memory (tmpfs).
-- Drupal caching was enabled.
-- APC caching was enabled for PHP.
-- Nginx served images/js/css files directly.
-- 10 PHP processes spawned for fast CGI.
-- Drupal installed with the standard profile - no additional modules enabled.
-- A single article was created for the frontpage.
Multiple AB runs were done at 10k requests and 10 concurrency. AB results shown below were recorded
when 'Requests per second' matched the majority of the 'Requests per second' result from other runs.
Other concurrency levels (as well as more fast CGI PHP processes) were tested with a similar pattern (~10 requests/sec improvement).
Note: The bottleneck in this test was PHP. MySQL itself handled the requests without breaking a sweat.
Mtop reported very low loads and top reported MySQL using only 5% CPU give or take a little.
Ping also reported healthy response times during the tests ( 0-1 MS with zero packet loss ).
Your results may vary.
AB test results:
*With extension:
Server Software: nginx/0.7.65
Server Hostname: 192.168.1.103
Server Port: 80
Document Path: /
Document Length: 4912 bytes
Concurrency Level: 10
Time taken for tests: 26.692 seconds
Complete requests: 10000
Failed requests: 0
Write errors: 0
Total transferred: 53070000 bytes
HTML transferred: 49120000 bytes
Requests per second: 374.64 [#/sec] (mean)
Time per request: 26.692 [ms] (mean)
Time per request: 2.669 [ms] (mean, across all concurrent requests)
Transfer rate: 1941.63 [Kbytes/sec] received
*Without extension:
Server Software: nginx/0.7.65
Server Hostname: 192.168.1.103
Server Port: 80
Document Path: /
Document Length: 4912 bytes
Concurrency Level: 10
Time taken for tests: 27.323 seconds
Complete requests: 10000
Failed requests: 0
Write errors: 0
Total transferred: 53070000 bytes
HTML transferred: 49120000 bytes
Requests per second: 366.00 [#/sec] (mean)
Time per request: 27.323 [ms] (mean)
Time per request: 2.732 [ms] (mean, across all concurrent requests)
Transfer rate: 1896.82 [Kbytes/sec] received
benchmarks/xhprof_diff_report_body.png

162 KiB

benchmarks/xhprof_diff_report_head.png

34.3 KiB

benchmarks/xhprof_drupal_static_with_extension.png

118 KiB

benchmarks/xhprof_drupal_static_without_extension.png

122 KiB

PHP_ARG_ENABLE(drupal_extension, whether to enable drupal_extension support,
[ --enable-drupal_extension Enable drupal_extension support])
if test "$PHP_DRUPAL_EXTENSION" != "no"; then
PHP_NEW_EXTENSION(drupal_extension, drupal_extension.c, $ext_shared)
fi
// $Id$
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "ext/standard/html.h"
#include "php_drupal_extension.h"
ZEND_DECLARE_MODULE_GLOBALS(drupal_extension)
/* True global resources - no need for thread safety here */
static int le_drupal_extension;
/* {{{ arginfo_drupal_static */
ZEND_BEGIN_ARG_INFO_EX(arginfo_check_plain, 0, 0, 1)
ZEND_ARG_INFO(0, text)
ZEND_END_ARG_INFO()
/* }}} */
/* {{{ arginfo_drupal_static */
ZEND_BEGIN_ARG_INFO_EX(arginfo_drupal_static, 0, 1, 1)
ZEND_ARG_INFO(0, name)
ZEND_ARG_INFO(0, default)
ZEND_ARG_INFO(0, reset)
ZEND_END_ARG_INFO()
/* }}} */
/* {{{ drupal_extension_functions[]
*
* Every user visible function must have an entry in drupal_extension_functions[].
*/
zend_function_entry drupal_extension_functions[] = {
PHP_FE(check_plain, arginfo_check_plain)
PHP_FE(drupal_static, arginfo_drupal_static)
{NULL, NULL, NULL} /* Must be the last line in drupal_extension_functions[] */
};
/* }}} */
/* {{{ drupal_extension_module_entry
*/
zend_module_entry drupal_extension_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
"drupal_extension",
drupal_extension_functions,
PHP_MINIT(drupal_extension),
PHP_MSHUTDOWN(drupal_extension),
PHP_RINIT(drupal_extension), /* Replace with NULL if there's nothing to do at request start */
PHP_RSHUTDOWN(drupal_extension), /* Replace with NULL if there's nothing to do at request end */
PHP_MINFO(drupal_extension),
#if ZEND_MODULE_API_NO >= 20010901
"0.1", /* Replace with version number for your extension */
#endif
STANDARD_MODULE_PROPERTIES
};
/* }}} */
#ifdef COMPILE_DL_DRUPAL_EXTENSION
ZEND_GET_MODULE(drupal_extension)
#endif
/* {{{ php_drupal_extension_init_globals
*/
static void php_drupal_extension_init_globals(zend_drupal_extension_globals *drupal_extension_globals)
{
// ALlocate memory and initialize hash tables for each page request.
ALLOC_HASHTABLE(drupal_extension_globals->drupal_static_zdata);
ALLOC_HASHTABLE(drupal_extension_globals->drupal_static_zdefault);
zend_hash_init(drupal_extension_globals->drupal_static_zdata, 0, NULL, NULL, 0);
zend_hash_init(drupal_extension_globals->drupal_static_zdefault, 0, NULL, NULL, 0);
}
/* }}} */
/* {{{ PHP_MINIT_FUNCTION
*/
PHP_MINIT_FUNCTION(drupal_extension)
{
return SUCCESS;
}
/* }}} */
/* {{{ PHP_MSHUTDOWN_FUNCTION
*/
PHP_MSHUTDOWN_FUNCTION(drupal_extension)
{
return SUCCESS;
}
/* }}} */
/* {{{ PHP_RINIT_FUNCTION
*/
PHP_RINIT_FUNCTION(drupal_extension)
{
// Initialize arrays to hold static variables on a per page request basis.
// Doing this in MINIT will make the same arrays persistent across multiple requests which is probably not wanted.
ZEND_INIT_MODULE_GLOBALS(drupal_extension, php_drupal_extension_init_globals, NULL)
return SUCCESS;
}
/* }}} */
/* {{{ PHP_RSHUTDOWN_FUNCTION
*/
PHP_RSHUTDOWN_FUNCTION(drupal_extension)
{
zend_hash_destroy(DRUPAL_EXTENSION_G(drupal_static_zdata));
zend_hash_destroy(DRUPAL_EXTENSION_G(drupal_static_zdefault));
FREE_HASHTABLE(DRUPAL_EXTENSION_G(drupal_static_zdata));
FREE_HASHTABLE(DRUPAL_EXTENSION_G(drupal_static_zdefault));
return SUCCESS;
}
/* }}} */
/* {{{ PHP_MINFO_FUNCTION
*/
PHP_MINFO_FUNCTION(drupal_extension)
{
php_info_print_table_start();
php_info_print_table_header(2, "drupal_extension support", "enabled");
php_info_print_table_end();
/* Remove comments if you have entries in php.ini
DISPLAY_INI_ENTRIES();
*/
}
/* }}} */
/* {{{ proto string check_plain(string text)
Experimental implementation of Drupal check_plain(). */
PHP_FUNCTION(check_plain)
{
char *str;
int str_len;
int len;
char *replaced;
zend_bool double_encode = 1;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &str, &str_len) == FAILURE) {
return;
}
replaced = php_escape_html_entities_ex(str, str_len, &len, 0, ENT_QUOTES, "UTF-8", double_encode TSRMLS_CC);
RETVAL_STRINGL(replaced, len, 0);
}
/* }}} */
/* {{{ proto string drupal_static(string arg)
Experimental implementation of drupal_static */
PHP_FUNCTION(drupal_static)
{
zval *name = NULL;
zval **drawer = NULL;
zval **default_drawer = NULL;
zval *new_drawer = NULL;
zval *zdeft = NULL;
zend_bool reset = 0;
HashTable *zdata = DRUPAL_EXTENSION_G(drupal_static_zdata);
HashTable *zdefault = DRUPAL_EXTENSION_G(drupal_static_zdefault);
char *key = NULL;
int key_len = 0;
int arg_len; // Currently only used for zend_parse_parameters().
ulong computed_key;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|zb", &name, &zdeft, &reset, &arg_len) == FAILURE) {
RETURN_FALSE;
}
if (Z_TYPE_P(name) == IS_NULL) {
// TODO: Finish implementing. Likely need to iterate thru the entire list of keys and call the zval destructor on each value.
// Calling zend_hash_clean before destructing its zval containers that were tied to PHP script variables will probably lead to problems.
RETURN_TRUE;
zend_hash_clean(zdata);
zend_hash_copy(zdata, zdefault, NULL, NULL, sizeof(zval *));
return;
} else if (Z_TYPE_P(name) != IS_STRING) {
// name holds the key and must be a string if not NULL. We have to bail out here to prevent segfaults and other horrors.
convert_to_string(name);
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid key (%s) specified. Key must be a string", Z_STRVAL_P(name));
RETURN_FALSE;
}
key = Z_STRVAL_P(name);
key_len = Z_STRLEN_P(name) + 1; // Hash functions want the entire string size including its trailing NULL character.
// Pre-compute the key's hash value so we can use the hash quick functions.
computed_key = zend_get_hash_value(key, key_len);
if (zend_hash_quick_find(zdata, key, key_len, computed_key, (void **)&drawer) == FAILURE) {
// drawer should be NULL if we got here, but there was a segfault and re-setting drawer to NULL seemed to fix it.
drawer = NULL;
// Handle the case where no default value was given.
if (zdeft == NULL) {
// zdeft gets initialized when the caller sets the returned reference to a value (array, scalar, etc).
zdeft = EG(uninitialized_zval_ptr);
}
zval_add_ref(&zdeft);
// Create a copy of the default value for the zdata array.
MAKE_STD_ZVAL(new_drawer);
*new_drawer = *zdeft;
zval_copy_ctor(new_drawer);
// Drawer is now a pointer to the newly stored value.
zend_hash_quick_add(zdata, key, key_len, computed_key, &new_drawer, sizeof(zval *), (void **)&drawer);
zend_hash_quick_add(zdefault, key, key_len, computed_key, &zdeft, sizeof(zval *), NULL);
}
if (reset) {
if (zend_hash_quick_find(zdefault, key, key_len, computed_key, (void **)&default_drawer) != FAILURE) {
// Free memory associated with the to-be-replaced key in the zdata array.
zval_ptr_dtor(drawer);
drawer = NULL;
// Create a copy of the default value stored for this key and put it in the zdata array.
MAKE_STD_ZVAL(new_drawer);
*new_drawer = **default_drawer;
zval_copy_ctor(new_drawer);
zend_hash_quick_update(zdata, key, key_len, computed_key, &new_drawer, sizeof(zval *), (void **)&drawer);
} else {
// Shouldn't happen. If the key was stored and the key in the default array is now missing then we have a problem.
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Can't reset key (%s). Default value not found!", key);
RETURN_FALSE;
}
}
// Make the stored value a reference so PHP's garbage collector doesn't delete it.
SEPARATE_ZVAL_TO_MAKE_IS_REF(drawer);
zval_add_ref(drawer);
// Return a reference to the stored value.
*return_value_ptr = *drawer;
}
/* }}} */
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: noet sw=4 ts=4 fdm=marker
* vim<600: noet sw=4 ts=4
*/
// $Id$
#ifndef PHP_DRUPAL_EXTENSION_H
#define PHP_DRUPAL_EXTENSION_H
extern zend_module_entry drupal_extension_module_entry;
#define phpext_drupal_extension_ptr &drupal_extension_module_entry
#ifdef PHP_WIN32
#define PHP_DRUPAL_EXTENSION_API __declspec(dllexport)
#else
#define PHP_DRUPAL_EXTENSION_API
#endif
#ifdef ZTS
#include "TSRM.h"
#endif
PHP_MINIT_FUNCTION(drupal_extension);
PHP_MSHUTDOWN_FUNCTION(drupal_extension);
PHP_RINIT_FUNCTION(drupal_extension);
PHP_RSHUTDOWN_FUNCTION(drupal_extension);
PHP_MINFO_FUNCTION(drupal_extension);
PHP_FUNCTION(check_plain);
PHP_FUNCTION(drupal_static);
ZEND_BEGIN_MODULE_GLOBALS(drupal_extension)
HashTable *drupal_static_zdata;
HashTable *drupal_static_zdefault;
ZEND_END_MODULE_GLOBALS(drupal_extension)
#ifdef ZTS
#define DRUPAL_EXTENSION_G(v) TSRMG(drupal_extension_globals_id, zend_drupal_extension_globals *, v)
#else
#define DRUPAL_EXTENSION_G(v) (drupal_extension_globals.v)
#endif
#endif /* PHP_DRUPAL_EXTENSION_H */
--TEST--
Check for drupal_extension presence
--SKIPIF--
<?php
// $Id$
if (!extension_loaded("drupal_extension")) print "skip";
?>
--FILE--
<?php
echo "drupal_extension extension is available";
/*
you can add regression tests for your extension here
the output of your test code has to be equal to the
text in the --EXPECT-- section below for the tests
to pass, differences between the output and the
expected text are interpreted as failure
see php5/README.TESTING for further information on
writing regression tests
*/
?>
--EXPECT--
drupal_extension extension is available
--TEST--
Check basic functionality of check_plain().
--SKIPIF--
<?php
// $Id$
if (!extension_loaded("drupal_extension")) print "skip";
?>
--FILE--
<?php
echo check_plain('test & <toto>');
?>
--EXPECT--
test &amp; &lt;toto&gt;
--TEST--
Test basic drupal_static() functionnality
--SKIPIF--
<?php if (!extension_loaded("drupal_extension")) print "skip"; ?>
--FILE--
<?php
// $Id$
$ret = &drupal_static("test", array());
if (is_array($ret) && empty($ret)) {
echo "Initial array value is preserved.\n";
}
$ret['test_value'] = 'toto';
$ret2 = &drupal_static("test", array());
if (isset($ret2['test_value']) && $ret2['test_value'] == 'toto') {
echo "Stored array value is preserved.\n";
}
$ret = &drupal_static('test', NULL, TRUE);
if (is_array($ret) && empty($ret)) {
echo "Reset key with array value successful.\n";
}
$ret = &drupal_static('test_scalar', 'default');
if (is_scalar($ret) && $ret == 'default') {
echo "Initial scalar value is preserved.\n";
}
$ret = 'default_changed';
$ret2 = &drupal_static('test_scalar');
if (is_scalar($ret2) && $ret2 == 'default_changed') {
echo "Stored scalar value is preserved.\n";
}
$ret = &drupal_static('test_scalar', NULL, TRUE);
if (is_scalar($ret) && $ret == 'default') {
echo "Reset key with scalar value successful.\n";
}
$ret = &drupal_static('test2', NULL);
$ret['foo_key'] = 'foo_value';
$ret = &drupal_static('test2');
if (is_array($ret) && isset($ret['foo_key']) && $ret['foo_key'] == 'foo_value') {
echo "Passed test with NULL as default.\n";
}
$failed = FALSE;
for($i = 0; $i < 50; $i++) {
if (loop_stability_test('loop_stability_test_key') == FALSE || loop_stability_test(get_rand_str()) == FALSE) {
$failed = TRUE;
break;
}
}
if (!$failed) {
echo "Passed loop stability test.\n";
}
function loop_stability_test($key) {
$ret = &drupal_static($key, array());
if (!is_array($ret) || !empty($ret)) {
return FALSE;
}
$ret["$key $key"] = $key;
$ret2 = &drupal_static($key);
if (!isset($ret2["$key $key"]) || $ret2["$key $key"] != $key) {
return FALSE;
}
$ret = &drupal_static($key, NULL, TRUE);
if (!is_array($ret) || !empty($ret)) {
return FALSE;
}
$ret = &drupal_static($key, NULL);
if (!is_array($ret) || !empty($ret)) {
return FALSE;
}
return TRUE;
}
function get_rand_str() {
$str = '';
for ($i=0; $i<10; $i++) {
$str .= chr(rand(65,90));
}
return $str;
}
?>
--EXPECT--
Initial array value is preserved.
Stored array value is preserved.
Reset key with array value successful.
Initial scalar value is preserved.
Stored scalar value is preserved.
Reset key with scalar value successful.
Passed test with NULL as default.
Passed loop stability test.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment