Wednesday, October 22, 2008

memcache namespace php? выход похоже найден

Наткнулся на.

http://code.google.com/p/memcached-tag/

Проект содержит патч к серверу memcached и патч к php-memcached расширению. В патче добавлен следующий функционал.

Есть таги. (Пространство имен)
Ключ можно привязать к тагу
tag_add tag key

Все ключи относящиеся к одному тагу можно махом грохнуть
tag_del tag

Если у тага не остается ни одного ключа, он убивается автоматически.

То что нужно, пойду собирать и пропихивать чтобы нам его в тестовом режиме начали ставить.
/me очень рад :)


Update Сбт Окт 25 00:49:01
Радость оказалась преждевременной.

1. Скачать, пропатчить, собрать не вызвало проблем хоть и не делал этого ни разу. Единственное пришлость переобозвать папку.
2. Заявленный функционал работает.
3. Проверил жалобы, не нашел, но на сайте проекта висит баг от May 02, 2008 http://code.google.com/p/memcached-tag/issues/detail?id=1 и за полгода до сих пор не поправлен.
4. Баг был мной повторен.
5. Сей код был отдан на растерзание нашему cpp отделу, квалификация товарищей сомнений не вызвает.
Резюме. Посоветовавшись с cpp отделом, решили что чем использовать эту поделку, они нам предоставят свою реализацию.

Жду, думаю несколько месяцев до тестов.

В качестве вариантов находили несколько реализаций на java. По отзывам товарищей все эти реализации медленнее memcached, из за обилия разных финтифлюшек, где namespaces самая мелкая финтифлюшка.



Пример создания trac macros

Cтавим trac (на примере debian)

sudo apt-get install trac


Подготавливаем папку для тестов

cd@cd-laptop: cd
cd@cd-laptop: mkdir tests
cd@cd-laptop: cd tests
cd@cd-laptop: mkdir tracdir
cd@cd-laptop: trac-admin tracdir initenv
жмем энтер :)
Запускаем через stand-along
cd@cd-laptop: tracd /home/cd/tests/tracdir --port 8888
cd@cd-laptop: firefox http://127.0.0.1:8888/tracdir



Трак установлен, для тестирования нам такого хватит.

останавливаем

cd@cd-laptop: Ctrl+C
cd@cd-laptop:


Создаем macro Hello

cd plugins
touch Hello.py



Содержимое Hello.py

from trac.core import *
from trac.wiki.macros import WikiMacroBase
from trac.util import escape

__all__ = ['HelloMacro']

class HelloMacro(WikiMacroBase):
def expand_macro(self,formatter, name, args):
return "Hello %s" % args or '

Использование. Внутри тикета или wiki напишите


[[Hello(cd)]]

Результат

Hello cd

Т.е. для написания минимального макроса, достаточно отнаследоваться от WikiMacroBase, реализовать один метод, возращающий html.

Пример ставшего для меня полезным макроса.

1 from trac.core import *
2 from trac.wiki.macros import WikiMacroBase
3 from trac.util import escape
4 from trac.util.html import escape,html,plaintext
5 from genshi.builder import tag
6
7
8 __all__ = ['XxxMacro']
9
10
11 class RwebtableMacro(WikiMacroBase):
12 """
13 Show table schema
14 {{{
15 [[Xxx(tablename)]]
16 }}}
17
18 """
19 def expand_macro(self, formatter, name, args):
20 # args will be `None` if the macro is called without parenthesis.
21 table = args or ''
22 if not table :
23 return 'no table specified'
24
25 import MySQLdb
26 user = self.env.config.get('cddb', 'user')#требует опции в trac.ini секции [cddb]
27 host = self.env.config.get('cddb', 'host')
28 passwd = self.env.config.get('cddb', 'passwd')
29 dbname = self.env.config.get('cddb', 'dbname')
30 db = MySQLdb.connect(user=user, host=host, passwd=passwd, db=dbname)
31 cursor = db.cursor()
32 sql = "DESCRIBE %s" % table
33 cursor.execute(sql)
34 table = tag.table(border=1)
35 table.append(tag.tr( tag.th('Field'), tag.th('Type') , tag.th('Null') , tag.th('Key') , tag.th('Default'), tag.th('Extra') ) )
36 res = cursor.fetchall()
37 for row in res :
38 tr = tag.tr()
39 for i in row :
40 tr.append(tag.td(str(i)) )
41 table.append(tr)
42 return str(table)




Например
[[Xxxtable(country)]]

FieldType Null Key Default Extra
country_idint(11)NOPRINoneauto_increment
isovarchar(3)YESNone
namevarchar(50)YESNone

Tuesday, October 21, 2008

hook pre-commit в svn. Реализация на питоне.

Задача : при коммите в svn, хочется чего то делать, например закрывать тикет в trac.

Для этого в svn есть механизм хуков. Далее по шагам как я это делал я.

Создание репозитория, начальный checkout

директория для тестов
cd@cd-laptop:~$ mkdir tests
cd@cd-laptop:~$ cd tests
директория для репозитория
cd@cd-laptop:~/tests$ mkdir repo
создание репозитория
cd@cd-laptop:~/tests$ svnadmin create repo
директория для чекаутов
cd@cd-laptop:~/tests$ mkdir checkout-dir
первый чекаут
cd@cd-laptop:~/tests$ svn checkout file:///home/cd/tests/repo/ checkout-dir/
Checked out revision 0.
создаем, добавляем, коммитим файл.
cd@cd-laptop:~/tests$ touch checkout-dir/x.txt
cd@cd-laptop:~/tests$ svn add checkout-dir/x.txt
checkout-dir/x.txt
cd@cd-laptop:~/tests$ svn ci checkout-dir/x.txt -m'first commit'
Adding checkout-dir/x.txt
Transmitting file data .
Committed revision 1.
cd@cd-laptop:~/tests$




Репозиторий создан, для теста сделал один коммит.

Теперь собственно создаем хук.


Заходим в папку репозитоия
cd@cd-laptop:~/tests$ cd repo/
смотрим чего есть. Интересует папка hooks
cd@cd-laptop:~/tests/repo$ ls
conf dav db format hooks locks README.txt
cd@cd-laptop:~/tests/repo$ cd hooks/
Заходим в нее.
cd@cd-laptop:~/tests/repo/hooks$ ls
post-commit.tmpl post-revprop-change.tmpl pre-commit.tmpl pre-revprop-change.tmpl start-commit.tmpl
post-lock.tmpl post-unlock.tmpl pre-lock.tmpl pre-unlock.tmpl
Создаем, делаем права на запуск, заполняем
cd@cd-laptop:~/tests/repo/hooks$ touch pre-commit
cd@cd-laptop:~/tests/repo/hooks$ chmod +x pre-commit
cd@cd-laptop:~/tests/repo/hooks$ cat > pre-commit
#!/usr/bin/python

import sys

sys.stderr.write('Test error')
sys.exit(1)

cd@cd-laptop:~/tests/repo/hooks$ cd ../..
cd@cd-laptop:~/tests$ echo '1' >> checkout-dir/x.txt
cd@cd-laptop:~/tests$ svn ci checkout-dir/x.txt -m'test'
Sending checkout-dir/x.txt
Transmitting file data .svn: Commit failed (details follow):
svn: 'pre-commit' hook failed with error output:
Test error
cd@cd-laptop:~/tests$



В папке репозитория в подпапке hoosk был создан файл pre-commit и ему поставлены права на запуск.
Теперь когда свн его увидит, будет запускать после получения данных непосредственно перед коммитом. При коде возврата не 0, commit не пройдет. Собственно сам скрипт можно писать на любом скриптовом языке, главное чтобы была шибанг строка.

Подробнее о коде pre-commit

#!/usr/bin/python

import sys

#пишем в STDERR
sys.stderr.write('Test error')
#выходим с кодом возврата 1
sys.exit(1)



Т.е. сам хук создан, он запускается, не дает нам сделать коммит, теперь хочется уметь работать с информацией, которая приходит перед коммитом. Нас интересуют такие вещи как
автор,
текст комментария,
содержимое файлов и т.д.


Здесь я маленько не разобрался, в основном попробовал методом тыка, поэтому напишу как я понял. Не факт что будет совсем правильно, но пользуясь тем как я это понял, у меня чего то получается делать.


Для получения информации о предстоящем коммите, полезна утилита

svnlook


Формат запуска следующий

svnlook SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]

Где subcommand одна из следующего списка

author
cat
changed
date
diff
dirs-changed
help (?, h)
history
info
lock
log
propget (pget, pg)
proplist (plist, pl)
tree
uuid
youngest



REPOS_PATH путь до репозитория, аргументы и опции могут зависеть от subcommand нас интересуют только две

any subcommand which takes the '--revision' and '--transaction'


--revision понятно, выполняет subcommand по определенной ревизии
--transaction интереснее, выполняет subcommand по транзакции, с этим я не разобрался, но понял так.

Если взглянуть на pre-commit.tpl (файл созданные в репозитории по умолчанию) увидим там следующее


REPOS="$1"
TXN="$2"

# Make sure that the log message contains some text.
SVNLOOK=/usr/bin/svnlook
$SVNLOOK log -t "$TXN" "$REPOS" | grep "[a-zA-Z0-9]" > /dev/null || exit 1


Где видно что параметр --transaction он же -t используется с переменной TXN, которая является вторым аргументом передаваемым в pre-commit. Т.е. эта штука у нас на руках, и svnlook умеет с ней работать.

Итак при обработке pre-commit у нас нет ревизии (мы ее создаем ) зато есть транзакция. Теперь что мы можем из этого получить.

Пример pre-commit который выводит автора и не дает закоммититься.


#!/usr/bin/python

import sys
import os

#обертка над svnlook для запуска любой команды
def cmd_out(subcommand, transaction, repo) :
cmd = '/usr/local/bin/svnlook %s -t "%s" "%s"' % (subcommand, transaction, repo)
str = os.popen(cmd, 'r').read()
return str.strip('\n')

#вызвает нашу оберку, для подкомманды author
def look_author(transaction, repo) :
return cmd_out('author', transaction, repo )

#читаем репозиторий, транзакцию
repos = sys.argv[1]
txn = sys.argv[2]

#получаем автора
author = look_author(txn, repos)

#пишем автора в STDERR
sys.stderr.write('Author :%s:' % author )
#выходим с ошибкой, запрещая коммит
sys.exit(1)


Единственное нужно указать правильный путь до svnlook

Ну теперь собственно все. Т.е. при помощи svnlook можно получить очень много информации о предстоящем коммите :)

Например получения текста лога для данного коммита.


def look_log (transaction, repo) :
return cmd_out('log', transaction, repo)


И так далее. Ну а наконец, скрипт который просматривает лог, ищет в определенном формате номер тикета, и пишет комментарий в трак к тикету, и закрывает тикет по необходимости.


#!/usr/bin/python

import sys, os, string, re

# return true or false if this passed string is a valid comment

php_devs = ('xx', 'xxx', 'yy', 'yyy', 'zz', 'zzz') #only for this dev's rules allowed



def cmd_out(subcommand, transaction, repo) :
cmd = '/usr/local/bin/svnlook %s -t "%s" "%s"' % (subcommand, transaction, repo)
str = os.popen(cmd, 'r').read()
return str.strip('\n')

def look_info(trans, repo) :
return cmd_out('tree --full-paths', trans, repo)

def look_author(transaction, repo) :
return cmd_out('author', transaction, repo )

def look_log (transaction, repo) :
return cmd_out('log', transaction, repo)

def check_comments(comment ):#return ticket id if find in comment
p = re.compile('\s*ticket:(\d+)(:close)?\s+(.*)$', re.MULTILINE)
res = p.match(comment)
try:
ticket_id = res.group(1)
except:
ticket_id = None
return ticket_id

def need_close(comment) : #i know it's copy paste, but i had choice, write into blog, or rewrite it ...
p = re.compile('\s*ticket:(\d+)(:(close))?\s+(.*)$', re.MULTILINE)
res = p.match(comment)
try:
is_closed = res.group(3)
except:
is_closed = ''
return is_closed == 'close'


def update_ticket_in_trac(ticket, author, comment, data ) :
import xmlrpclib
s = xmlrpclib.ServerProxy('http://tracuser:tracpast@domain.com:port/path_to_Trac/login/xmlrpc')#create service
try:
ticketInfo = s.ticket.get(ticket)#try get ticket
status = ticketInfo[3]['status']
if status == 'closed' :
sys.stderr.write("Ticket already closed, need reopen it first !!!")
return 1
except: #can catch here protocol err, it mean no rights
sys.stderr.write("No ticket found in trac for id %s" % ticket )
return 1
comment = comment.replace(':close','')
s.ticket.update(ticket,comment.replace("ticket:%s" % ticket, '%s killed himself with message : \n' % author , data) , data)
return 0


def main(repos, txn):
comment = look_log(txn, repos)
author = look_author(txn,repos)
if not (author in php_devs) :
return 0
ticket = check_comments(comment)
if ticket == None :
sys.stderr.write("\nticket:num must be at the comment begin where num is ticket id, if not exists create it please!")
return 1
data = dict()
if ( need_close(comment) ) :
data['status'] = 'closed'
return update_ticket_in_truck(ticket, author,comment, data )

if __name__ == '__main__':
if len(sys.argv) < 3 :
sys.stderr.write("Usage: %s REPOS TXN\n" % (sys.argv[0]))
else:
sys.exit(main(sys.argv[1], sys.argv[2]))


Friday, October 17, 2008

Php, не все так плохо как кажется на первый взгляд.

   С полгода назад, пришлось реализовать для удобства IArrayAccess. И только после реализации решил замерить насколько оно медленнее массива, и запомнилась мне следующая удивительная цифра. Что то на 10.000 итераций Объект реализовавший IArrayAccess был медленнее на десяток секунд. (На не сильно слабой машине).


   Решил сегодня это печальное событие задокументировать с цифрами.  Результат приятно удивил.

Исходники
array.php

error_reporting(E_ALL);
$array = array();

for ( $k = 1; $k < 10; $k++)
{
unset($array);
$array = array();

for ($i = 0 ; $i < 100000; $i++)
{
$array[$i] = $i + $k;
}
}
?>




spl-array.php

class SplArrayTest implements ArrayAccess
{
protected $_data;

public function __construct()
{
$this->_data = array();
}

public function offsetExists($offset)
{
return isset($this->_data[$offset]);
}

public function offsetGet($offset)
{
return $this->_data[$offset];
}

public function offsetSet($offset, $value)
{
$this->_data[$offset] = $value;
}
public function offsetUnset($offset)
{
unset($this->_data[$offset]);
}

public function __destruct()
{
unset($this->_data);
}
}


$array = new SplArrayTest();

for ( $k = 1; $k < 10; $k++)
{
unset($array);
$array = new SplArrayTest();

for ($i = 0 ; $i < 100000; $i++)
{
$array[$i] = $i + $k;
}
}

?>



spl2-array.php

class SplArrayTest implements ArrayAccess
{
public function __construct()
{
}

public function offsetExists($offset)
{
return isset($this->$offset);
}

public function offsetGet($offset)
{
return $this->$offset;
}

public function offsetSet($offset, $value)
{
$this->$offset = $value;
}
public function offsetUnset($offset)
{
$this->$offset;
}

public function __destruct()
{
}
}


$array = new SplArrayTest();

for ( $k = 1; $k < 10; $k++)
{
unset($array);
$array = new SplArrayTest();

for ($i = 0 ; $i < 100000; $i++)
{
$array[$i] = $i + $k;
}
}
?>



PHP 5.2.4-2ubuntu5.3  (2 ядра, системя с запущенными иксам, не сильно загружена)


Запуск через time php array.php
Среднее время выполнения - 1.6 секунды
Среднее время выполнения при включенном xcache  - 1.3
На нашем прод сервере + apc = 0.5 секунды

Запуск через time php spl-array.php
Среднее время выполнения - 3.9 секунды
Среднее время выполнения при включенном xcache  - 3.72
На нашем прод сервере + apc = 1.2 секунды

Запуск через time php spl2-array.php
Время выполнения 4.7 секунды
Среднее время выполнения при включенном xcache  - 4.2


Выводы.

На 1.000.000 элементарных операций разница всего на 2-3 секунды. На моем ноуте.
Разница в полсекунды на прод сервере.

Для примера в zend framework по умолчанию  порядка 2.000-4.000 таких и не очень элементарных вызовов.
В простейшей реализации страницы на limb
10.000 и выше. (за счет их любимых тулкитов).

Т.е. получается что для обычной страницы вы потеряете при активном использовании SPL IArrayAccess сотые доли секунды. Чем можно пренебрегать очень смело.

В итоге бояться использовать SPL  я перестаю, хотя все равно на душе будет не спокойно.



Thursday, October 16, 2008

сервера

  Долгое время не было времени ни на что.

  Сначала провозился с переездом хостинга, настройкой веб серверов. Долго писать не охота. Поэтому вкратце.

 1. Линух без свапа - говно.
 2. FreeBsd на малых ресурсах - рулит.
 3. Апач на малых ресурсах неадекватен.
 4. lighthttpd - рулит :)


Зато в перерыве устав нажимать ctrl +F для поиска в программе футбола, сваял. http://tv.quicknotes.ru/

Monday, September 29, 2008

django fixtures использование

Изучаю джанго.

Понадобились fixtures. Полез в новую джанго-книжку, написано много, как использовать. А как их создавать непонятно.
В итоге.

Пусть есть модель page для ее есть админка, лежит в application yyy

Я делал так.


    1. Создал необходимые данные для этой модели через админку.
    2. Перешел в директорию проекта набрал ./manage.py dumpdata --indent=2  yyy > ./data/fixtures/xxx.json
    3. Прописал в settings.py FIXTURE_DIRS = (  PROJECT_ROOT + '/data/fixtures/',)
    4. И уже при установке сначала ./manage syncdb
    5. Затем ./manage.py loaddata xxx

Tuesday, September 23, 2008

Конвертирование ошибок в exceptions.

Задача
Преобразовать все ошибки в исключения (exceptions) для более удобной их обработки.
Решение подсмотрено на php.net в комментариях.

class My_Exception extends Exception {
public static function errorHandlerCallback($code, $string, $file, $line, $context)
{
$e = new self($string, $code);
$e->line = $line;
$e->file = $file;
$e->context = $context;
throw $e;
}

}

set_error_handler(array("My_Exception", "errorHandlerCallback"), E_ALL);



Теперь практически все ошибки будут преобразованны в Exception. Теперь достаточно легко и непринужденно мы можем отлавливать ошибки php в одном месте, например



// bla bla bla
try
{
$controller->dosmth();
}
catch( Controller_Wrong_Data_Exeption $e)
{
// do smth
}
catch ( My_Exception $e)
{
// we know it's php error
}



Такой метод не ловит фаталы, а очень хочется. Где то встречал решение с использованием особенности ob_start, ob_flush, если найду завтра добавлю.
 
Каталог сайтов, Добавить сайт