2017-05-06 09:13:33 +00:00
|
|
|
<?php
|
|
|
|
namespace devilbox;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @requires devilbox::Logger
|
|
|
|
*/
|
2017-05-15 06:56:17 +00:00
|
|
|
class Memcd extends BaseClass implements BaseInterface
|
2017-05-06 09:13:33 +00:00
|
|
|
{
|
|
|
|
/*********************************************************************************
|
|
|
|
*
|
|
|
|
* Private Variables
|
|
|
|
*
|
|
|
|
*********************************************************************************/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Memcached instance
|
|
|
|
* @var object|null
|
|
|
|
*/
|
|
|
|
private $_memcached = null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*********************************************************************************
|
|
|
|
*
|
2017-05-15 06:56:17 +00:00
|
|
|
* Constructor Overwrite
|
2017-05-06 09:13:33 +00:00
|
|
|
*
|
|
|
|
*********************************************************************************/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Use singleton getInstance() instead.
|
|
|
|
*
|
|
|
|
* @param string $user Username
|
|
|
|
* @param string $pass Password
|
|
|
|
* @param string $host Host
|
|
|
|
*/
|
2017-05-15 06:56:17 +00:00
|
|
|
public function __construct($hostname, $data = array())
|
2017-05-06 09:13:33 +00:00
|
|
|
{
|
2017-05-15 06:56:17 +00:00
|
|
|
parent::__construct($hostname, $data);
|
|
|
|
|
2021-04-19 15:24:11 +00:00
|
|
|
// Faster check if memcached is not loaded
|
|
|
|
if (!$this->isAvailable()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-06-27 11:14:02 +00:00
|
|
|
if (class_exists('Memcached')) {
|
|
|
|
$memcd = new \Memcached('_devilbox');
|
|
|
|
$list = $memcd->getServerList();
|
|
|
|
|
|
|
|
if (empty($list)) {
|
|
|
|
$memcd->setOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE, true);
|
2018-12-10 19:09:09 +00:00
|
|
|
$memcd->setOption(\Memcached::OPT_BINARY_PROTOCOL, true);
|
2017-06-27 11:14:02 +00:00
|
|
|
$memcd->addServer($hostname, 11211);
|
|
|
|
}
|
2017-05-06 09:13:33 +00:00
|
|
|
|
2017-06-27 11:14:02 +00:00
|
|
|
$err = false;
|
|
|
|
$stats = $memcd->getStats();
|
|
|
|
if (!isset($stats[$hostname.':11211'])) {
|
|
|
|
$memcd->quit();
|
|
|
|
$this->_connect_error = 'Failed to connect to Memcached host on '.$hostname.' (no connection array)';
|
|
|
|
$this->_connect_errno = 1;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else if (!isset($stats[$hostname.':11211']['pid'])) {
|
|
|
|
$memcd->quit();
|
|
|
|
$this->_connect_error = 'Failed to connect to Memcached host on '.$hostname.' (no pid)';
|
|
|
|
$this->_connect_errno = 2;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else if ($stats[$hostname.':11211']['pid'] < 1) {
|
|
|
|
$memcd->quit();
|
|
|
|
$this->_connect_error = 'Failed to connect to Memcached host on '.$hostname.' (invalid pid)';
|
|
|
|
$this->_connect_errno = 3;
|
|
|
|
return;
|
|
|
|
}
|
2018-12-10 19:09:09 +00:00
|
|
|
$memcd->getDelayed(array('devilbox-version'));
|
|
|
|
if (!$memcd->fetchAll()) {
|
|
|
|
$memcd->set('devilbox-version', $GLOBALS['DEVILBOX_VERSION'].' ('.$GLOBALS['DEVILBOX_DATE'].')');
|
|
|
|
}
|
2017-06-27 11:14:02 +00:00
|
|
|
$this->_memcached = $memcd;
|
|
|
|
} else {
|
|
|
|
|
|
|
|
$ret = 0;
|
2018-12-15 16:04:26 +00:00
|
|
|
loadClass('Helper')->exec('printf "stats\nquit\n" | nc '.$hostname.' 11211', $ret);
|
2017-06-27 11:14:02 +00:00
|
|
|
if ($ret == 0) {
|
|
|
|
$this->_memcached = true;
|
|
|
|
}
|
|
|
|
}
|
2017-05-06 09:13:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Destructor
|
|
|
|
*/
|
|
|
|
public function __destruct()
|
|
|
|
{
|
2017-06-27 11:14:02 +00:00
|
|
|
if (class_exists('Memcached')) {
|
|
|
|
if ($this->_memcached) {
|
|
|
|
$this->_memcached->quit();
|
|
|
|
}
|
2017-05-06 09:13:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-15 06:56:17 +00:00
|
|
|
|
|
|
|
|
2017-05-06 09:13:33 +00:00
|
|
|
/*********************************************************************************
|
|
|
|
*
|
2017-05-15 06:56:17 +00:00
|
|
|
* Select functions
|
2017-05-06 09:13:33 +00:00
|
|
|
*
|
|
|
|
*********************************************************************************/
|
|
|
|
|
2017-05-07 16:49:38 +00:00
|
|
|
|
|
|
|
public function getKeys()
|
2017-05-06 09:13:33 +00:00
|
|
|
{
|
2017-05-07 16:49:38 +00:00
|
|
|
$store = array();
|
2018-12-15 16:04:26 +00:00
|
|
|
|
|
|
|
// CLI seems to only sometimes get the results, so we will just loop a bit
|
|
|
|
// It's a very quick operation anyway.
|
|
|
|
$cli_retries = 100;
|
|
|
|
|
|
|
|
// Memcached >= 1.5
|
|
|
|
for ($i=0; $i<$cli_retries; $i++) {
|
|
|
|
|
2019-03-01 12:59:33 +00:00
|
|
|
// Get item number to trigger with stats cachedump
|
2018-12-15 16:04:26 +00:00
|
|
|
$output = array();
|
2019-03-01 12:59:33 +00:00
|
|
|
exec('printf "stats items\nquit\n" | nc memcd 11211 | grep -E \'items:[0-9]+:number\s[0-9]+\'', $output);
|
|
|
|
$num1 = 1;
|
|
|
|
$num2 = 0;
|
|
|
|
if (isset($output[0])) {
|
|
|
|
$matches = array();
|
|
|
|
preg_match('/items:([0-9]+):number\s([0-9]+)/', $output[0], $matches);
|
|
|
|
if (isset($matches[1])) {
|
|
|
|
$num1 = $matches[1];
|
|
|
|
}
|
|
|
|
if (isset($matches[2])) {
|
|
|
|
$num2 = $matches[2];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Trigger stats cachedump on item number
|
|
|
|
$output = array();
|
|
|
|
exec('printf "stats cachedump '.$num1.' '.$num2.' \nquit\n" | nc memcd 11211 | grep -E \'^ITEM\'', $output);
|
2018-12-15 16:04:26 +00:00
|
|
|
foreach ($output as $line) {
|
|
|
|
$matches = array();
|
|
|
|
preg_match('/(^ITEM)\s*(.+?)\s*\[([0-9]+\s*b);\s*([0-9]+\s*s)\s*\]/', $line, $matches);
|
|
|
|
$key = $matches[2];
|
|
|
|
$store[] = array(
|
|
|
|
'key' => $key,
|
|
|
|
'val' => $this->_memcached->get($key),
|
|
|
|
'ttl' => $matches[4],
|
|
|
|
'size' => $matches[3],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
// If we actually got a result, we can break here
|
|
|
|
if (count($store)) {
|
|
|
|
return $store;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// This will only work for Memcached < 1.5
|
2017-06-27 11:14:02 +00:00
|
|
|
if (class_exists('Memcached')) {
|
|
|
|
if ($this->_memcached) {
|
2018-12-10 19:09:09 +00:00
|
|
|
|
2018-12-15 16:04:26 +00:00
|
|
|
// Ensure we retrieve data not in binary
|
|
|
|
$this->_memcached->setOption(\Memcached::OPT_BINARY_PROTOCOL, false);
|
|
|
|
|
|
|
|
if (!($keys = $this->_memcached->getAllKeys())) {
|
|
|
|
$keys = array();
|
2017-11-05 18:00:34 +00:00
|
|
|
}
|
2018-12-15 16:04:26 +00:00
|
|
|
$this->_memcached->getDelayed($keys, true);
|
|
|
|
$data = $this->_memcached->fetchAll();
|
|
|
|
if (is_array($data)) {
|
|
|
|
for ($i=0; $size=count($data), $i<$size; $i++) {
|
|
|
|
$store[$i]['key'] = $data[$i]['key'];
|
|
|
|
$store[$i]['val'] = $data[$i]['value'];
|
|
|
|
$store[$i]['ttl'] = '?';
|
|
|
|
$store[$i]['size'] = strlen($data[$i]['value']);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Revert Memcachd protocol
|
|
|
|
$this->_memcached->setOption(\Memcached::OPT_BINARY_PROTOCOL, true);
|
2017-05-07 16:49:38 +00:00
|
|
|
}
|
2017-05-06 09:13:33 +00:00
|
|
|
}
|
2017-05-07 16:49:38 +00:00
|
|
|
return $store;
|
2017-05-06 09:13:33 +00:00
|
|
|
}
|
|
|
|
|
2017-05-07 16:49:38 +00:00
|
|
|
public function getInfo()
|
2017-05-06 09:13:33 +00:00
|
|
|
{
|
2017-05-08 07:22:10 +00:00
|
|
|
$stats = array();
|
2017-06-27 11:14:02 +00:00
|
|
|
if (class_exists('Memcached')) {
|
|
|
|
if ($this->_memcached) {
|
|
|
|
$stats = $this->_memcached->getStats();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$ret = 0;
|
2018-12-15 16:04:26 +00:00
|
|
|
$output = loadClass('Helper')->exec('printf "stats\nquit\n" | nc memcd 11211 | sed "s/^STAT[[:space:]]*//g" | grep -v "END"', $ret);
|
2017-06-27 11:14:02 +00:00
|
|
|
if ($ret == 0) {
|
|
|
|
$output = explode("\n", $output);
|
|
|
|
foreach ($output as $line) {
|
|
|
|
$tmp = explode(' ', $line);
|
|
|
|
$key = isset($tmp[0]) ? $tmp[0] : '';
|
|
|
|
$val = isset($tmp[1]) ? $tmp[1] : '';
|
2018-12-10 19:09:09 +00:00
|
|
|
$stats['memcd'][$key] = $val;
|
2017-06-27 11:14:02 +00:00
|
|
|
}
|
|
|
|
}
|
2017-05-08 07:22:10 +00:00
|
|
|
}
|
2017-05-07 16:49:38 +00:00
|
|
|
return $stats;
|
|
|
|
}
|
|
|
|
|
2017-05-06 09:13:33 +00:00
|
|
|
|
|
|
|
/*********************************************************************************
|
|
|
|
*
|
|
|
|
* Interface required functions
|
|
|
|
*
|
|
|
|
*********************************************************************************/
|
|
|
|
|
2017-05-15 06:56:17 +00:00
|
|
|
|
|
|
|
private $_can_connect = array();
|
|
|
|
private $_can_connect_err = array();
|
|
|
|
|
|
|
|
private $_name = null;
|
|
|
|
private $_version = null;
|
|
|
|
|
|
|
|
public function canConnect(&$err, $hostname, $data = array())
|
|
|
|
{
|
|
|
|
$err = false;
|
|
|
|
|
|
|
|
// Return if already cached
|
|
|
|
if (isset($this->_can_connect[$hostname])) {
|
|
|
|
// Assume error for unset error message
|
|
|
|
$err = isset($this->_can_connect_err[$hostname]) ? $this->_can_connect_err[$hostname] : true;
|
|
|
|
return $this->_can_connect[$hostname];
|
|
|
|
}
|
|
|
|
|
2018-12-10 19:09:09 +00:00
|
|
|
$ret = 0;
|
2018-12-15 16:04:26 +00:00
|
|
|
loadClass('Helper')->exec('printf "stats\nquit\n" | nc '.$hostname.' 11211', $ret);
|
2018-12-10 19:09:09 +00:00
|
|
|
if ($ret == 0) {
|
|
|
|
$this->_can_connect[$hostname] = true;
|
2017-06-27 11:14:02 +00:00
|
|
|
} else {
|
2018-12-10 19:09:09 +00:00
|
|
|
$err = 'Failed to connect to Memcached host on '.$hostname;
|
|
|
|
$this->_can_connect[$hostname] = false;
|
2017-05-15 06:56:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return $this->_can_connect[$hostname];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-05-06 09:13:33 +00:00
|
|
|
public function getName($default = 'Memcached')
|
|
|
|
{
|
|
|
|
return $default;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getVersion()
|
|
|
|
{
|
2017-05-15 06:56:17 +00:00
|
|
|
// Return if already cached
|
|
|
|
if ($this->_version !== null) {
|
|
|
|
return $this->_version;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return empty if not available
|
|
|
|
if (!$this->isAvailable()) {
|
|
|
|
$this->_version = '';
|
|
|
|
return $this->_version;
|
|
|
|
}
|
|
|
|
|
2017-06-27 11:14:02 +00:00
|
|
|
if (class_exists('Memcached')) {
|
|
|
|
|
|
|
|
if ($this->_memcached) {
|
|
|
|
$info = $this->_memcached->getVersion();
|
|
|
|
$info = array_values($info);
|
|
|
|
if (!isset($info[0])) {
|
|
|
|
loadClass('Logger')->error('Could not get Memcached version');
|
|
|
|
$this->_version = '';
|
|
|
|
} else {
|
|
|
|
$this->_version = $info[0];
|
|
|
|
}
|
2017-05-08 07:22:10 +00:00
|
|
|
}
|
2017-06-27 11:14:02 +00:00
|
|
|
} else {
|
2018-12-15 16:04:26 +00:00
|
|
|
$version = loadClass('Helper')->exec('printf "version\nquit\n" | nc memcd 11211 | grep -oE "[0-9.-]+"', $ret);
|
2017-06-27 11:14:02 +00:00
|
|
|
$this->_version = $version;
|
2017-05-06 09:13:33 +00:00
|
|
|
}
|
2017-05-15 06:56:17 +00:00
|
|
|
return $this->_version;
|
2017-05-06 09:13:33 +00:00
|
|
|
}
|
2021-04-19 15:24:11 +00:00
|
|
|
|
|
|
|
public function isAvailable()
|
|
|
|
{
|
|
|
|
if (extension_loaded('memcached')) {
|
|
|
|
return parent::isAvailable();
|
|
|
|
}
|
|
|
|
|
|
|
|
// when php module 'memcached' not available or just disable by configuration (see .env PHP_MODULES_DISABLE)
|
|
|
|
return false;
|
|
|
|
}
|
2017-05-06 09:13:33 +00:00
|
|
|
}
|