понедельник, Апрель 06, 2009

Файловая система Btrfs

Это конспект моего доклада на семинаре, организованном нашей LUG совместно с университетом. Опять же, времени было пшик, так что доклад весьма обзорный.


Введение

Речь пойдёт о файловой системе нового поколения. Традиционно ФС играла значительную роль в организации Unix-систем. И во многом именно свойствами ФС определялись свойства той или иной реализации Unix.

Файловая система должна хранить файлы и обеспечивать доступ к ним. При этом к ней предъявляется большое количество требований, зачастую взаимоисключающих: поддержка файлов любого размера, высокая производительность операций ввода/вывода, масштабируемость и т.д. Давно стало ясно, что ни одна файловая система не может быть одинаково эффективна во всех случаях. Поэтому все современные реализации Unix поддерживают работу с несколькими типами ФС одновременно. Есть такое выражение: "Linux - это Unix сегодня", и ядро Linux поддерживает свыше 50 (!) типов ФС.

ФС нового поколения

В 2005-м году компания Sun Microsystems представила файловую систему ZFS, которая стала прорывом в области файловых систем. Из-за лицензионной политики Sun ZFS не может быть включена в ядро Linux. Однако в 2007-м году началась разработка файловой системы нового поколения для Linux - Btrfs. Разработку оплачивает компания Oracle, однако код выпускается под лицензией GNU GPL и входит в ядро Linux начиная с релиза 2.6.29, вышедшего на этой неделе.

Приведу фрагмент интервью Chris Mason - основного разработчика Btrfs:

  • Опишите Btrfs своими словами.

  • Btrfs - это новая файловая система, выпускаемая под GPL, которая разрабатывается с учётом масштабируемости на очень большие объёмы носителей. Масштабируемость означает не только возможность адресовать блоки носителя, но также возможность работать с повреждениями данных и метаданных. Это означает наличие инструментов для проверки и восстановления файловой системы без отмонтирования, и интегрированную проверку контрольных сумм, чтобы определять ошибки.

  • Является ли Btrfs наследницей какой-нибудь другой ФС?

  • Да, всех их :) Здесь много идей из ReiserFS, отложенное размещение и другие идеи из XFS. ZFS популяризовала идею, что подсчёт контрольных сумм данных может быть быстрым, и что управление логическими томами может быть лучше. Идеи по реализации управления томами пришли из AdvFS.

Основные возможности Btrfs

Итак, основные возможности, которые будут в Btrfs:

  • Поддержка доступных на запись снапшотов (аналог клонов ZFS). Кроме того, здесь можно создавать снапшоты снапшотов.

  • Поддержка субтомов --- множественных именованных корней в одной файловой системе с общим пулом хранения.

  • Поддержка сложных многодисковых конфигураций --- RAID уровней 0, 1, 5, 6 и 10, а также реализация различных политик избыточности на уровне объектов ФС --- то есть возможно назначить, к примеру, зеркалирование для какого-либо каталога или файла.

  • Copy-on-write (CoW) журналирование.

  • Контроль целостности блоков данных и метаданных с помощью контрольных сумм.

  • Зеркалирование метаданных даже в однодисковой конфигурации.

  • Полностью распределенное блокирование.

  • Поддержка ACL.

  • Защита от потери данных.

  • Выбор хэш-алгоритма.

  • Поддержка NFS.

  • Флаги совместимости, необходимые для изменения дискового формата в новых версиях btrfs с сохранением совместимости со старыми.

  • Резервные копии суперблока, по крайней мере --- по одной на устройство.

  • Скоростные приоритеты для дисков.

  • Гибридные пулы. btrfs старается перемещать наиболее используемые данные на самое быстрое устройство, вытесняя с него "залежавшиеся" блоки. Эта политика хорошо согласуется с появившейся недавно моделью использования SSD (Solid State Drive).

  • Балансировка данных между устройствами в btrfs возможна сразу после добавления диска к пулу, отдельной командой --- а не только постепенно, в процессе использования (как это реализовано в ZFS).

  • Диски для горячей замены, поддержка которых появилась и в ZFS.

  • Он-лайн конфигурирование RAID будет реализовано на уровне объектов файловой системы --- субтомов, снапшотов, файлов. Возможно будет также устанавливать некоторые параметры ввода-вывода для каталогов --- с наследованием этих свойств всеми дочерними объектами.

  • Конвертер из ext2/3/4.

Большинство из этих возможностей уже реализованы и работают.

Принципы устройства

С точки зрения устройства ФС, можно выделить следующие основные моменты, которые делают возможными все перечисленные особенности в сочетании с очень хорошей производительностью:

  • B-деревья везде, где они имеют смысл

  • Copy-on-write везде, где это имеет смысл

  • Политика блокировок - высокая гранулярность блокировок.

B-деревья и дали название файловой системе (B-tree FS). B-деревья - это сильноветвящиеся деревья (B-деревья почему-то часто путают с двоичными деревьями, видимо, из-за буквы B, но она означает Block; у каждого узла в B-дереве обычно несколько тысяч потомков), каждый узел которых, в свою очередь, содержит большое количество записей (они обычно организуются в двоичное сбалансированное дерево; в частности, в Btrfs используются красно-чёрные деревья). Читаются и пишутся узлы B-дерева целиком, что даёт значительный выигрыш в производительности.

Copy-on-write (CoW) - это алгоритм, предназначенный для ситуаций, когда нужно создавать много похожих объектов. Рассмотрим, например, создание снапшота ФС. Снапшот - это мгновенная копия всех данных части ФС в данный момент времени. Реализация "в лоб" предусматривает создание копий всех файлов, что займёт много времени и много дискового пространства. При использовании CoW создаётся только один новый объект - копия корневого каталога, а на всех файлах, на которые ссылается корневой, ставится специальная метка. Когда приложение пытается писать в "помеченный" каталог, ФС прозрачно для приложения делает его копию (помечая при этом все файлы в этом каталоге), и приложение пишет уже в созданную копию. Таким образом, создаются копии только изменившихся данных, и только тогда, когда данные действительно изменяются. Это позволяет сделать операцию создания снапшотов почти мгновенной даже для очень больших разделов. Это немаловажно, т.к. одно из основных требований к операции создания снапшота - атомарность, т.е. эта операция не должна прерываться (и конфликтовать) никакими другими операциями. В Btrfs CoW используется не только при создании снапшотов, но и при ведении журнала и многих внутренних операциях.

Блокировки - это сущность, позволяющая избежать конфликтов при одновременном доступе к данным из разных потоков. Поток, который хочет внести изменение в некоторую структуру данных, сначала проверяет, не заблокирована ли она другим потоком; если заблокирована - ждёт, пока блокировка не будет освобождена, иначе сам захватывает блокировку, и освобождает её по окончании записи. Когда речь идёт о доступе к сложным структурам данных, возникает вопрос о политике блокировок. Нужно ли блокировать всю структуру целиком, или каждый элемент в отдельности, или элементы какими-то группами? Чем крупнее единица блокировки, тем меньше блокировок, и меньше накладных расходов. Чем мельче - тем более эффективно расходуется процессорное время, т.к. потокам приходится ждать гораздо меньше. Btrfs стремится блокировать как можно меньшие элементы структур (но не слишком мелкие).

Ещё один пункт, связанный с блокировками, специфичен для ядра. В ядре есть два вида блокировок: spin-lock и мьютексы. При использовании spin-lock ожидающий поток "крутится" в бесконечном цикле. При использовании мьютексов - поток переходит в заблокированное состояние TASK_INTERRUPTIBLE, и "пробуждается" планировщиком автоматически при освобождении блокировки. Понятно, что мьютексы более эффективны, т.к. не тратят процессорное время на пустые циклы. Но мьютексы не могут быть использованы в контексте обработчика прерывания, т.к. в этом состоянии планировщик не работает. Значительная часть функций любой ФС может быть вызвана как из обработчика прерывания, так и в контексте задачи. Поэтому во многих функциях приходится использовать менее эффективные спин-блокировки.

Btrfs использует новый тип блокировок, которые могут работать в обоих режимах (и их можно переключать между режимами). Таким образом, один и тот же код будет использовать мьютексы в контексте задачи и спин-блокировки в режиме прерывания.

Ещё одна особенность Btrfs: все структуры ФС могут находиться в произвольных местах раздела, они связаны между собой указателями. Этой особенностью обладают и некоторые другие ФС, но разработчики Btrfs нашли ей новое применение: конвертация разделов из других ФС (сейчас реализована конвертация из ext2/3, конвертер из ext4 в разработке, теоретически можно создать конвертеры из других ФС). При конвертации структуры Btrfs создаются в местах раздела, помеченных в исходной ФС как свободные. В Btrfs создаётся специальный файл, в который входят блоки, занятые структурами исходной ФС. Таким образом, эти блоки оказываются помеченными как занятые. Кроме того, этот файл представляет собой образ исходной ФС, который можно примонтировать (mount -o loop). Это позволяет выполнить откат к предыдущей ФС. Чтобы освободить место на диске, достаточно просто удалить файл с образом исходной ФС (возможность отката, соответственно, пропадёт).

Одна из особенностей современных ФС не так давно вызвала небольшой скандал. Дело в том, что в ядрах unix (и linux) код ФС не занимается непосредственно записью данных на диск. Данные записываются в страницы памяти, и эти страницы помечаются как "грязные", и затем их сбрасывает на диск отдельный поток ядра (pdflush). Так вот, при использовании современных ФС (в той новости речь шла про ext4, но теми же свойствами обладают и XFS, и Btrfs, и многие другие) интервал между записью данных в страничный кэш и их записью на диск может достигать 150 секунд (больше двух минут). Unix традиционно пишется для хорошего оборудования. В частности, предполагается, что в любой системе, от которой нужна надёжность, применяются UPS. Поэтому большая задержка записи является не недостатком, а преимуществом: это даёт возможность разместить данные более удачно, и избежать фрагментации. А при использовании на менее надёжном оборудовании нужно просто перенастроить ядро средствами sysctl, чтобы заставить pdflush срабатывать чаще.

Btrfs vs Ext4

Другая недавно вышедшая ФС для Linux - это Ext4. Она учитывает многие наработки современных ФС (экстенты, delayed allocation итп), но при этом основана на коде Ext3. Это более продвинутая ФС, чем та же ext3, по тестам она во многих случаях даёт бОльшую производительность и может быть рекомендована для использования на многих машинах уже сейчас. Но при этом её не назовёшь ФС нового поколения: архитектура осталась от ext3.

Btrfs сейчас в стадии experimental, разработчики предупреждают, что сейчас её имеет смысл использовать только для тестирования и экспериментов. Даже дисковый формат до сих пор окончательно не устаканился. Но при этом это безусловно ФС нового поколения, по архитектуре она напоминает разве что ZFS, но не старые ФС linux-ядра.

Btrfs vs ZFS

Больше всего Btrfs похожа на ZFS от компании Sun. Btrfs не поддерживает диски такого астрономического объёма, как zfs, но вряд ли это в ближайшее время будет иметь практическое значение. Зато Btrfs имеет некоторые возможности, отсутствующие в zfs: снапшоты снапшотов и скоростные приоритеты дисков, оптимизацию для ssd-накопителей. Но ZFS уже вовсю используется на production-серверах, а использование btrfs станет массовым, видимо, года через два (если предполагать, что распространение btrfs будет развиваться также, как zfs).

Измерения производительности Btrfs сейчас мало информативны, т.к. оптимизация в разгаре. Однако уже сейчас Btrfs обходит zfs по производительности некоторых операций.

Текущее состояние разработки

Основные возможности Btrfs уже реализованы. Дисковый формат близок к стабилизации, если он и будет меняться, то не сильно. Только что завершена реализация обработки ситуации нехватки места на диске (проблема в том, что фактическая запись на диск может происходить уже после закрытия файла программой, и было не совсем очевидно, как передать ошибку записи программе). Вовсю идёт поиск и исправление других ошибок. В разработке специальный ioctl-API для поддержки транзакционного I/O (несколько операций, объединённых в транзакцию, могут быть выполнены все или не выполнены совсем; кроме всего прочего, это позволяет минимизировать количество проверок между операциями в одной транзакции). Ближайшая задача - реализация удаления снапшотов, первый вариант кода уже появился в рассылке.

суббота, Февраль 14, 2009

Обзор свободного математического ПО

Это конспект моего доклада на семинаре, организованном нашей LUG совместно с университетом. Соответственно, я не мог охватить всё - у меня на доклад было где-то 15 минут.



Вступление

Известные пакеты - это гиганты всё-в-одном

Когда мы говорим о математическом ПО, на ум приходят такие гиганты, как Maple, Mathematica, MatLAB… У них есть одно общее свойство: они пытаются охватить всё. Конечно, Mathematica известна прежде всего как система для символьных вычислений, а Matlab - для численных, но одновременно в Mathematica есть мощные алгоритмы для вычислений с плавающей точкой, а в Matlab - пакет для символьных вычислений. Причём эти второстепенные функции в программах по сравнению с программами, для этого предназначенными, выглядят убого и смешно. А небезызвестный MathCAD пытается включить в себя всё, при этом всё реализовано так себе. Причина проста: нельзя объять необъятное.

Свободные программы - делают одно дело хорошо

В противоположность этому, большинство свободных программ следует философии UNIX, гласящей: программа должна делать одно дело, но делать его хорошо. Свободного математического ПО очень много, при этом бóльшая часть их предназначена для какой-нибудь одной задачи. Например, есть программы, которые только и умеют, что строить сетку для метода конечных разностей. Или программа, которая предназначена для вычисления цифр числа Пи. Или программа, которая умеет только строить графики, но зато очень хорошо.

Однако, есть и программы, в той или иной степени являющиеся аналогами известных пакетов. Я расскажу о трёх.

Символьные вычисления: Maxima

История проекта

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

Сначала я напомню, что компьютеры - это, вообще-то, Электронные Вычислительные Машины, они создавались для вычислений над числами. Однако уже в конце 50-х появилась идея, что можно заставить компьютер работать не только с числами, но и с алгебраическими выражениями. В начале 60-х начали появляться первые системы компьютерной алгебры. И, конечно, такая система нужна была одному мирному американскому ведомству (департаменту энергетики, это практически подразделение Пентагона). Был объявлен тендер, и его выиграл проект под названием Macsyma (пишется через CS). В течение многих лет DOE Macsyma развивалась как коммерческий проект, финансируемый правительством. В 1982-м году Уильям Шелтер создал форк Macsyma, называемый Maxima. В начале 90-х распался СССР, кончилась холодная война, и косвенным следствием этого стало практически полное прекращение финансирования DOE Macsyma. К концу 90-х проект практически загнулся. Исходники Macsyma по кусочкам распродали, и они оказались в Maple и Mathematica. В 1998-м Уильям Шелтер добился от DOE разрешения на публикацию исходных текстов Maxima под лицензией GPL. Maxima стала свободной программой. В 2001-м Шелтер скончался, но к этому моменту над Maxima работало уже довольно много людей, и они подхватили проект.

Интерфейс: командная строка или wxMaxima

Maxima имеет традиционный для UNIX интерфейс командной строки, однако также умеет слушать сетевой порт, работая как сервер. Этот факт используют различные оболочки (фронтенды), предоставляющие графический интерфейс. Наиболее распространены TeXmacs и wxMaxima. TeXmacs - это научный текстовый редактор, в котором можно в документ вставить сессию Maxima. wxMaxima выглядит примерно так:

wxmaxima.png

Последняя версия, 0.8.0, стала больше походить на Mathematica и Maple: раньше командная строка для ввода была отдельно, внизу.

Lisp-подобный язык

Язык Maxima берёт основные идеи из Lisp, так как Maxima написана на Lisp-e. При этом он похож одновременно на языки Mathematica и Maple, так как эти программы позаимствовали многие идеи и часть кода из Macsyma. Чтобы избежать долгого и нудного перечисления возможностей, я приведу пример решения типичных задач с первого курса.

Пример

Пусть дана функция

maxima>> f(x) := x*tanh(x) + x + 1/x + 2;

img_6c5de9b1e9.png

Проверим, не является ли она чётной или нечётной:

maxima>> f(-x);

img_c6e53363be.png

Как видим, функция не является ни чётной, ни нечётной. Найдём пределы функции на плюс-минус бесконечности:

maxima>> limit(f(x),x,-inf);

img_fa950582c8.png

maxima>> limit(f(x),x,inf);

img_87feb85b90.png

Итак, на плюс бесконечности функция уходит в бесконечность. Нет ли у неё наклонной асимптоты?

maxima>> limit(f(x)/x, x,inf);

img_fa950582c8.png

Наклонная асимптота есть - y=kx+b, причём k=2. Найдём b:

maxima>> limit(f(x)-2*x, x,inf);

img_fa950582c8.png

Наконец, построим график:

maxima>> plot2d(f(x), [x,-5,5], [y,-10,10]);

plot_994344a98e.png

Найдём производную нашей функции:

maxima>> diff(f(x),x);

img_f6d2ff964a.png

И заодно - неопределённый интеграл:

maxima>> integrate(f(x), x);

img_d616f08f2e.png

Интеграл до конца "не взялся". Можно показать, что этот интеграл в элементарных функциях и не берётся. Однако Maxima умеет брать некоторые из таких интегралов, используя специальные функции:

maxima>> part: risch(x/(exp(2*x)+1), x);

img_af26a97797.png

(здесь я присваиваю результат интегрирования переменной part). Таким образом, интеграл f(x) будет равен

maxima>> ir: -2*part + log(x) + x^2 + 2*x;

img_647150032b.png

Что-то ужасное. Раскроем скобки:

maxima>> expand(ir);

img_4a9ed800a2.png

Дифференциальные уравнения

Или вот пример более сложных вычислений. Пусть надо решить дифференциальное уравнение:

maxima>> eq: 'diff(y,x) + x*y = 1-x^2;

img_4d77ef0914.png

Знак апострофа здесь используется, чтобы указать, что не надо сейчас вычислять производную, а сохранить обозначение.

maxima>> solution: ode2(eq,y,x);

img_3700eead6d.png

Вот и решение. erf здесь - это специальная функция, известная как функция ошибки Лапласа. После раскрытия скобок получим вот что:

maxima>> expand(solution);

img_64b81111f9.png

По Maxima есть некоторое количество русскоязычных руководств, которые можно найти в интернете. На мой взгляд, самое удачное введение с обзором возможностей содержится в цикле статей Тихона Тарнавского в журнале LinuxFormat. Сейчас эти статьи выложены в открытый доступ, в том числе на русском сайте Maxima. Документация по продвинутым возможностям maxima существует, к сожалению, только на английском языке. Официальная документация составляет 712 страниц.

Численные вычисления: Scilab

Scilab совместим с MatLAB-ом

Наиболее известный пакет для численных расчётов - это MatLAB. Scilab создавался как конкурент matlab-а, более скромный по ценовой политике. Однако коммерчески проект себя не оправдал, и исходные коды были открыты под лицензией, похожей на GNU GPL. Язык scilab сделан по возможности совместимым с матлабом, так что большинство ваших наработок из matlab заработают в scilab. Только вот, как известно, основная мощь matlab-a сосредоточена в его тулбоксах - отдельно поставляемых модулях. Модули для scilab-а тоже есть, однако их сильно меньше.

scilab.png

Octave - это GPL-аналог Matlab

Позже появился проект GNU Octave, нацеленный на создание аналога matlab-a, распространяемого по GNU GPL без всяких заморочек. Язык тоже практически совместим с матлабом, но здесь нет аналога Simulink - средства моделирования и симулирования динамических систем.

Зато Octave имеет чисто консольный интерфейс (конечно, графические фронтенды тоже есть, самый развитый - QtOctave), что позволяет использовать его в скриптах, для автоматизации расчётов, и упрощает встраивание в сложные программные комплексы. Для Octave написаны десятки пакетов расширений.

По Scilab есть статьи на русском языке, кроме того, не так давно в издательстве AltLinux вышла книга `Scilab: Решение инженерных и математических задач'. Книгу можно приобрести в интернет-магазине, кроме того, её электронная версия свободно доступна на сайте AltLinux.

Обработка данных: GNU R

Обзор

Формально, средства обработки данных относятся к программам для численных расчётов, ибо всё что они делают - это вычисления над числами. Однако, как известно, специализированный инструмент всегда лучше универсального. Под словами обработка данных скрывается довольно много различных видов деятельности: статистический анализ, статистическое моделирование, выборка только нужных данных, преобразование данных, построение различных графиков и гистограмм.

Программы для обработки данных можно разделить по типичному размеру выборки, для которого они предназначены. Для небольших выборок подойдёт, например, Statistica. Для средних по размеру выборок хорошо подходит GNU R (она хранит все данные в оперативной памяти, так что на типичном PC получим ограничение в 1-2-4 гигабайта). Для больших и очень больших объёмов данных (от сотен гигабайт до сотен терабайт) предназначены разработанные в CERN свободные системы PAW и ROOT.

GNU R - это интерпретируемый язык программироваммирования, предназначенный для статистического анализа и моделирования. R - это свободная реализация давно существующего языка S. Язык этот весьма эклектичен, он местами похож на C, местами - на Python, местами - на Haskell. Для GNU R существует почти полторы тысячи пакетов расширений (написанных на самом R, на C или Fortran), собранных в репозитории CRAN (Comprehensive R Archive Network).

Типы данных - числа, строки, факторы, векторы, списки и таблицы данных

Основные типы данных в языке - это числа, строки, факторы, векторы, списки и таблицы данных (data frames). Фактор - это данные, которые могут принимать одно из нескольких значений (пол; сорт дерева; логический тип и др). Векторы являются аналогами массивов - это набор из нескольких значений одного типа, размер вектора меняться не может. Тут же надо заметить, что в R нету скаляров; например, число - это, с точки зрения R, вектор из одного элемента. Списки - это обобщение векторов, они могут содержать объекты разных типов, и длина их может меняться. Кроме того, отдельным элементам списка можно присвоить имена, и обращаться к элементам не по номерам, а по именам. Пример:

R>> lst <- list(1,2,3)

(присваивание в R обозначается обычно знаком , хотя можно использовать и более привычное =; кроме того, есть форма value → variable). Для обращения к элементам списка по номеру используются двойные квадратные скобки:

R>> lst[[2]]
[1] 2

Назначим имена элементам списка:

R>> names(lst) <- c('first','second','third')

(функция c создаёт векторы). Теперь к элементам списка можно обращаться по именам:

R>> lst$third
[1] 3

Таблица данных (фрейм данных) в R - это список, состоящий из векторов. Создаются таблицы данных чаще всего загрузкой из внешнего файла.

Пример

Скажем, в файле airquality.dat находятся данные замеров качества воздуха:

"Ozone" "Solar.R" "Wind" "Temp" "Month" "Day"
"1" 41 190 7.4 67 5 1
"2" 36 118 8 72 5 2
"3" 12 149 12.6 74 5 3
"4" 18 313 11.5 62 5 4
"5" NA NA 14.3 56 5 5
"6" 28 NA 14.9 66 5 6
"7" 23 299 8.6 65 5 7
"8" 19 99 13.8 59 5 8
"9" 8 19 20.1 61 5 9
"10" NA 194 8.6 69 5 10
.......................

В первой строке - названия полей, дальше идут сами данные. Пропущенные (неизвестные) данные обозначены как NA. Загрузим эти данные в R:

R>> air <- read.table('airquality.dat', sep=' ', header=TRUE)

Здесь мы указываем имя файла, разделитель (пробел), а также указываем, что в первой строке записаны имена полей. К полям таблицы мы можем теперь обращаться как к элементам списка - например, air$Ozone. Посмотрим, что R знает о структуре наших данных:

R>> str(air)
'data.frame':       153 obs. of  6 variables:
$ Ozone : int 41 36 12 18 NA 28 23 19 8 NA ...
$ Solar.R: int 190 118 149 313 NA NA 299 99 19 194 ...
$ Wind : num 7.4 8 12.6 11.5 14.3 14.9 8.6 13.8 20.1 8.6 ...
$ Temp : int 67 72 74 62 56 66 65 59 61 69 ...
$ Month : int 5 5 5 5 5 5 5 5 5 5 ...
$ Day : int 1 2 3 4 5 6 7 8 9 10 ...

Теперь мы можем, например, посмотреть описательную статистику по всем полям таблицы:

R>> summary(air)
    Ozone           Solar.R           Wind             Temp
Min. : 1.00 Min. : 7.0 Min. : 1.700 Min. :56.00
1st Qu.: 18.00 1st Qu.:115.8 1st Qu.: 7.400 1st Qu.:72.00
Median : 31.50 Median :205.0 Median : 9.700 Median :79.00
Mean : 42.13 Mean :185.9 Mean : 9.958 Mean :77.88
3rd Qu.: 63.25 3rd Qu.:258.8 3rd Qu.:11.500 3rd Qu.:85.00
Max. :168.00 Max. :334.0 Max. :20.700 Max. :97.00
NA's : 37.00 NA's : 7.0
Month Day
Min. :5.000 Min. : 1.00
1st Qu.:6.000 1st Qu.: 8.00
Median :7.000 Median :16.00
Mean :6.993 Mean :15.80
3rd Qu.:8.000 3rd Qu.:23.00
Max. :9.000 Max. :31.00

Для каждого поля показаны минимум, максимум, медиана и две квартили, среднее значение и количество пропущенных данных. Осталось только среднеквадратичное отклонение:

R>> sd(air)
Ozone  Solar.R     Wind     Temp    Month      Day
NA NA 3.523001 9.465270 1.416522 8.864520

Как видим, R считает среднеквадратичное отклонение для полей Ozone и Solar.R неизвестным - из-за того, что в этих полях есть пропущенные данные. Мы можем явно указать, что на пропущенные данные не надо обращать внимание:

R>> sd(air, na.rm=TRUE)
    Ozone   Solar.R      Wind      Temp     Month       Day
32.987885 90.058422 3.523001 9.465270 1.416522 8.864520

Построим простейшую линейную модель - исследуем зависимость концентрации озона от температуры:

R>> ot <- lm(Ozone ~ Temp, data=air)
R>> ot
Call:
lm(formula = Ozone ~ Temp, data = air)
Coefficients:
(Intercept) Temp
-146.995 2.429

То есть, если приближать зависимость линейной Ozone = k*Temp + b, то k=2.429, а b=-146.995, при увеличении температуры концентрация озона в среднем растёт.

По GNU R есть довольно много материалов на русском, в частности, методические рекомендации по лабораторным работам для вузов. Также есть хорошее введение в R, содержащееся в цикле статей А.Б. Шипунова и Е.М.Балдина в журнале LinuxFormat, сейчас эти статьи есть в открытом доступе. Продвинутая документация, к сожалению, только на английском, зато её много, включая толстые книги. Официальное руководство к R занимает 2541 страницу.

четверг, Январь 01, 2009

Некоторые хитрости в использовании xmonad

Некоторое время назад я публиковал здесь статьи по настройке ion3. Всё течёт, всё меняется, и сейчас я использую другой фреймовый оконный менеджер - xmonad. С русской документацией по нему сейчас дело обстоит лучше, чем обстояло с ion3, когда я начал писать о нём. Именно, есть довольно основательная статья xmonad: функциональный оконный менеджер. Так что с вопросами "что такое xmonad" отсылаю туда. Однако, когда есть одна только вводная документация - этого всё-таки недостаточно. Хочется примеров настройки и всяческих вкусностей. И их есть у меня! ;)

Во вводных статьях по xmonad обычно рассматриваются три стандартные "компоновки" (способа автоматического расположения окон): Full, Tall и Mirror Tall. "Контрибы" xmonad содержат ещё довольно много компоновок, однако даже базовые могут использоваться более чем одним способом. Например, компоновку "Tall 1 (1/100) (2/3)" (что означает: одно мастер-окно, занимающее по ширине 2/3 экрана, за раз ширина его может меняться на 1/100) я использую для чтения документов и книг: основную часть экрана занимает окно документа, а сбоку может быть что-то ещё. Конечно, такую компоновку можно сделать и "на ходу" из стандартной Tall несколькими нажатиями (по умолчанию) mod-l, но если есть уже сделанная заготовка - проще обратиться к ней. Итак, "хитрость" первая: делайте "заготовки" из настроенных компоновок, чтобы потом быстро к ним обращаться.

Для того, чтобы удобнее было обращаться к конкретным компоновкам, есть расширение XMonad.Layout.Named. Делаем

import XMonad.Layout.Named

и потом в определении layoutHook описываем компоновки, давая им имена. Например, вместо tiled пишем named "dwmtiled" tiled, где "dwmtiled" - выбираемое вами имя компоновки.

По умолчанию для переключения компоновок используются сочетания mod-space (следующая компоновка) и mod-shift-space (предыдущая). Однако, когда компоновок больше чем 2-3, это становится неудобно. Удобнее переключаться сразу на нужную компоновку. Я использую для этого сочетания клавиш типа mod+буква. Чтобы такое себе устроить, подправьте в xmonad.hs строку с импортом модуля XMonad: вместо "import XMonad" напишите

import XMonad hiding ( (|||) )

Это мы указали, что не хотим использовать оператор ||| (служащий для перечисления компоновок), определённый в модуле XMonad. Зато мы будем использовать одноимённый оператор, определённый в модуле LayoutCombinators. Итак,

import XMonad.Layout.LayoutCombinators

Оператор ||| из LayoutCombinators "умнее", и позволяет переключаться сразу на нужную компоновку. Теперь описываем сочетания клавиш для этого переключения:

...
, ((modMask, xK_d ), sendMessage $ JumpToLayout "dwmtiled")
, ((modMask, xK_m ), sendMessage $ JumpToLayout "mirror")
...

где "dwmtiled", "mirror" - имена соответствующих компоновок.

Однако переключение на указанную компоновку - только побочная задача модуля LayoutCombinators. Главное его назначение состоит, соответственно названию, в том, чтобы комбинировать компоновки. Этот модуль содержит операторы типа ***||**. Такие операторы разбивают экран на две части, в каждой из которых работает своя компоновка. Количество звёздочек слева и справа показывает, в каком отношении разбивать экран (скажем, упомянутый оператор делит экран в отношении 3:2). Операторы с вертикальными чертами (|) делят экран по вертикали, а с наклонными (например, ***//*) - по горизонтали. Операторы, в которых две черты (вертикальные или наклонные), позволяют во время работы изменять соотношение частей экрана (перетаскивая границу мышкой), а операторы с одной чертой (например, */***) - не позволяют.

Одна проблема с LayoutCombinators состоит в том, что для перемещения окон между разными частями экрана стандартные действия (swapUp, swapDown) не работают. Для этого приходится использовать модуль WindowNavigation, который определяет модификатор компоновки windowNavigation и действие Move (с аргументом U/D/L/R, указывающим, куда двигать окно).

Вот пример использования LayoutCombinators:

-- Разделить экран по вертикали в отношении 3:1
onebig = windowNavigation (tile ***|* coltile)
where
-- компоновка для левой части
-- master-окно занимает 3/4 по высоте
tile = Mirror $ Tall 1 (1/100) (3/4)
-- компоновка для правой части
-- располагает все окна в один столбец
coltile = Tall 0 (1/100) (1/2)

Здесь onebig - это компоновка, дающая одному окну большую часть экрана (3/4 по вертикали и 3/4 по горизонтали), а остальные располагающая снизу и справа от него. Кому легче один раз увидеть, чем десять раз прочитать - вот пример использования этой компоновки (заодно это иллюстрация к предыдущей статье).

Ещё одна "хитрость" касается автоматического назначения свойств окнам (manageHook). xmonad по умолчанию делает диалоги "плавающими" (float), и это правильно. Только вот по умолчанию распознаются не все диалоги. В частности, по умолчанию xmonad не считает диалогами всплывающие окна Gimp-а (например, диалог кривых и пр). Однако это можно победить. Для таких окон приложения обычно выставляют свойство окна _NET_WM_WINDOW_TYPE в значение _NET_WM_WINDOW_TYPE_DIALOG. Можно заставить xmonad проверять это свойство:

-- подключаем библиотеки X11
import Graphics.X11.Xlib.Extras
import Foreign.C.Types (CLong)
-- Взять значение свойства окна
getProp :: Atom -> Window -> X (Maybe [CLong])
getProp a w = withDisplay $ \dpy -> io $ getWindowProperty32 dpy a w
-- Эта функция проверяет, выставлено ли свойство окна name в значение value
checkAtom name value = ask >>= \w -> liftX $ do
a <- getAtom name
val <- getAtom value
mbr <- getProp a w
case mbr of
Just [r] -> return $ elem (fromIntegral r) [val]
_ -> return False
-- Эта функция проверяет, является ли окно диалогом
checkDialog = checkAtom "_NET_WM_WINDOW_TYPE" "_NET_WM_WINDOW_TYPE_DIALOG"

Другой "пунктик" - надо ещё распознавать "отрывающиеся" (tear-off) меню. Это тоже можно сделать проверкой значения атома:

checkMenu = checkAtom "_NET_WM_WINDOW_TYPE" "_NET_WM_WINDOW_TYPE_MENU"

Объявляем соответствующие manageHook-и и добавляем их к остальным:

-- Сделать меню плавающими
manageMenus = checkMenu --> doFloat
-- Сделать диалоги плавающими
manageDialogs = checkDialog --> doFloat
-- Добавляем наши функции к остальным
myManageHook = ... <+> manageMenus <+> manageDialogs

Xmonad реализует концепцию виртуальных десктопов (здесь они называются workspaces), как, вобщем, и большинство других оконных менеджеров. Однако известна также другая концепция - теги для окон. Теги используются, например, в dwm и awesome. Их можно использовать и в xmonad. У меня сейчас используются обе концепции параллельно.

Чтобы использовать теги в xmonad, нужно подключить соответствующий модуль:

import XMonad.Actions.TagWindows

Я объявляю несколько функций, для пущей читабельности:

-- переместить окна, помеченные тегом name, на текущий workspace
showtag name = withTaggedGlobalP name shiftHere
-- вкл/выкл тег name для текущего окна
toggletag name = withFocused $ \w -> hasTag name w >>=
(\b -> if b then delTag name w else addTag name w)
-- снять тег name
remtag name = withFocused (delTag name)
-- перейти к следующему окну, помеченному тегом name
nexttagged name = focusDownTaggedGlobal name
-- переместить окна с тегом name с текущего workspace на "misc"
shiftoff name = withTaggedP name (W.shiftWin "misc")

Т.к. я использую несколько тегов, то для объявления сочетаний клавиш для перечисленных действий я ввожу отдельную функцию:

-- Объявить сочетания клавиш для тега tag с клавишей tag
tagkeys mask key tag = [
((mod1Mask, key), showtag tag),
((mask, key), toggletag tag),
((mod3Mask, key), nexttagged tag),
((mask .|. controlMask, key), shiftoff tag)
]

(mod3 у меня находится слева от цифрового ряда клавиатуры). Ну и добавляем эти сочетания к остальным:

...
-- Пометить текущее окно произвольным тегом
, ((modMask, xK_t), tagPrompt defaultXPConfig (\s -> withFocused (addTag s)))
-- Снять произвольный тег
, ((modMask .|. controlMask, xK_t), tagDelPrompt defaultXPConfig)
-- Переместить окна, помеченные произвольным тегом, на текущий workspace
, ((mod1Mask, xK_t), tagPrompt defaultXPConfig (\s -> withTaggedGlobalP s shiftHere))
-- Вкл/выкл тег "mark"
, ((modMask, xK_grave), toggletag "mark")
-- Перейти к следующему окну, помеченному "mark"
, ((mod3Mask, xK_grave), focusDownTaggedGlobal "mark")
...
]
++ (tagkeys modMask xK_exclam "web")
++ (tagkeys modMask xK_numbersign "text")
++ (tagkeys modMask xK_slash "gfx")
++ (tagkeys modMask xK_semicolon "office")
++ (tagkeys modMask xK_colon "docs")
++ (tagkeys modMask xK_question "math")
++ (tagkeys modMask xK_asterisk "files")
++ (tagkeys modMask xK_percent "im")

(у меня typewriter-like раскладка клавиатуры, так что при нажатии цифровых клавиш без шифта получаются знаки препинания).

Для полного счастья надо, чтобы некоторым окнам (отбираемым, например, по классу или заголовку) сразу назначались правильные теги. Стандартного manageHook-а для этого нет, так что приходится изобретать свой. Чтобы было покороче, я просто приведу куски своего xmonad.hs:

import XMonad.Hooks.XPropManage
...
myManageHook = ignoresome <+> (xPropManageHook xPropMatches) <+> manageMenus <+> manageDialogs
ignoresome = composeAll
[ className =? "trayer" --> doIgnore
, className =? "fbpanel" --> doIgnore
, className =? "Plasma" --> doIgnore]
xPropMatches :: [XPropMatch]
xPropMatches = tagclasses ["Epiphany-browser", "Kontact", "Liferea-bin"] "web"
++ tagclasses ["gimp", "f-spot", "Inkscape", "Eog"] "gfx"
++ tagclasses ["gnome-terminal"] "term"
++ tagclasses ["Gedit", "Leafpad", "Gvim"] "text"
++ tagclasses ["Evince"] "docs"
++ tagclasses ["Nautilus"] "files"
++ tagclasses ["Amarok", "Rhythmbox", "Totem"] "media"
++ tagclasses ["Wxmaxima"] "math"
++ moveclasses ["Pidgin"] "im"
++ floatclasses ["Qwerty.py"]
where
ckClass cls = [(wM_CLASS, any (cls==))]
-- добавить тег окну
tag name = pmX (addTag name)
-- добавить тег и переместить окно
moveAndTag name = (\w -> addTag name w >> return (W.shift name))
mkfloat = pmX float
-- пометить тегом все окна с данным классом
tagclasses clss name = [ (ckClass cls, tag name) | cls <- clss ]
-- переместить окна с данным классом на воркспейс ws
moveclasses clss ws = [ (ckClass cls, moveAndTag ws) | cls <- clss ]
floatclasses clss = [ (ckClass cls, mkfloat) | cls <- clss ]

Это, конечно, далеко не все "фишки" xmonad. Однако, я надеюсь, кому-то это может послужить стартовой площадкой :)

вторник, Декабрь 23, 2008

Все форматы документа из одного исходника: asciidoc сотоварищи

Я уже давно использую asciidoc для написания сколько-нибудь больших текстов. Почти все статьи в этом блоге, включая эту, подготовлены с помощью Asciidoc.

Asciidoc - это транслятор простейшего языка разметки текста в любой другой язык разметки. Разметка asciidoc очень простая, практически вы пишете plain text, только выделяете заголовки знаками = в начале строки, полужирный текст - *звёздочками*, курсив - 'кавычками', итд. Абзацы разделяются пустой строкой. А на выходе может быть всё что угодно, это зависит от так называемого backend-a, поведение которого описывается в конфиге. В поставке доступны бэкенды для xhtml, html4 и docbook. Docbook, в свою очередь, теоретически можно отконвертировать во что угодно.

На днях я готовил доклад для одного семинара, и мне хотелось получить его сразу в нескольких форматах: html и pdf, как минимум. И ещё бы надо к нему презентацию… И хорошо бы план доклада. И, конечно, не хочется для каждого формата готовить текст.

HTML (точнее, xhtml 1.1) делается с помощью asciidoc. Все остальные форматы, теоретически, можно получить из docbook, который можно получить с помощью asciidoc. Только вот на практике мне так и не удалось за полдня заставить ни один из конверторов docbook нормально работать с русскими буквами. Также в комплекте asciidoc есть экспериментальный бэкенд latex, но он как-то странно работает с кусками кода, которые мне нужно поместить в tex-файл в неизменном виде (речь идёт о формулах): половина формул куда-то проглатываются.

Кроме всего прочего, мне нужно в доклад включать фрагменты диалога с консольными программами (в данном случае - с maxima и с R). Так как в ходе подготовки доклада что-то может меняться, неохота каждый раз делать copy-paste из консоли. Надо бы, чтобы в исходник вставлять только запросы к программам - а вызываются программы и вставляется вывод пусть автоматически.

В общем, в итоге я написал скрипт lmaxima.py, который делает следующее: читает входной файл, и копирует его в выходной. Если встречает строку вида "program>> команды", то по пайпу передаёт эти команды указанной программе, и её ответ вставляет в выходной файл. Если встречает строку вида "program|tex>> команды" - то указанные команды оборачивает в функцию tex(). Таким образом, lmaxima.py работает как препроцессор для asciidoc. Одна из тонкостей состоит в том, как вставлять в документ формулы, которые выдаёт maxima. Если выводить надо в html, то формулы пропускаются через tex, и в выходной файл вставляется картинка (строка image:chtoto.png[]). Если же выводить надо в pdf, то lmaxima указывается ключ -i, и в выходной файл вставляется непосредственно tex-код.

Т.к. latex-бэкенд к asciidoc работает странно, пришлось писать свой конвертер из подмножества asciidoc-разметки в tex (благо, основная часть разметки asciidoc очень простая). Называется он у меня vsml.py. Заодно vsml.py умеет следующее:

  • С ключом -c - добавляет в документ оглавление (latex-овская команда \tableofcontents),

  • с ключом -p - "выдирает" из исходника только заголовки, и составляет содержание документа (план доклада, в моём случае),

  • с ключом -b - создаёт исходник для презентации (класс beamer); в презентацию попадают заголовки и картинки.

vsml понимает ещё и некоторые "надстройки" над синтаксисом asciidoc. Так, с помощью строчек "//{" и "//}" (asciidoc их воспринимает как комментарии) можно создавать вложенные куски текста. По умолчанию они выводятся как обычно, однако vsml.py можно задать ключ -l с числовым параметром, и он будет выводить только текст с "уровнем вложенности" не больше заданного; это позволяет оформлять более и менее необязательные части текста, и из одного исходника создавать документы разной степени подробности. А с помощью строчки вида "//.Тут заголовок" можно создавать (под)заголовки, которые не будут видны нигде, кроме презентации.

Конечно, вручную писать все эти команды с ключами каждый раз долго, поэтому я написал небольшой Makefile:

all: report.pdf report.html presentation.pdf plan.pdf
clean:
rm report.pdf report.html presentation.pdf
rm presentation.tex report.asciidoc report.vsml
rm plan.tex plan.pdf
plan.pdf: plan.tex
pdflatex $<
plan.tex: report.vsml
vsml.py -p < $< > $@
report.pdf: report.tex
pdflatex $<
presentation.pdf: presentation.tex
pdflatex $<
report.html: report.asciidoc
asciidoc $<
report.asciidoc: math-report
lmaxima.py $< $@
presentation.tex: report.vsml
vsml.py -b < $< > $@
report.tex: report.vsml
vsml.py < $< > $@
report.vsml: math-report
lmaxima.py -i $< $@


PS: мне тут подсказывают: добавь ещё festival, оно за тебя и доклад прочитает :)

воскресенье, Ноябрь 23, 2008

Создание собственных виджетов в PyGTK с помощью cairo

Свободная библиотека Gtk, как известно, не отличается очень большим выбором виджетов. Но никто не мешает создавать свои собственные.

Gtk, как известно, построена на принципах ООП, что хорошо ложится на объектную модель Python. В данном случае это означает, что наследование виджетов естественным образом соответствует наследованию классов в Питоне. Так, создав класс-потомок gtk.VBox, мы получим виджет со всеми свойствами VBox, и сможем добавлять в него нужную функциональность.

Покажу простейший пример. Пусть мы хотим создать виджет, выглядящий как комбинация gtk.Label и gtk.Entry, т.е. поле для ввода сразу с подписью. Чтобы сделать такое непосредственно средствами gtk, нужно создать gtk.HBox, а в него поместить Label и Entry. Т.е. HBox окажется родительским виджетом для всей конструкции. Вот от него и будем наследоваться:

class LabeledEntry(gtk.HBox):

Но наш виджет довольно сильно отличается от простого HBox, поэтому нужно переопределить инициализатор:


  def __init__(self,label=None):
gtk.HBox.__init__(self) # Вызываем инициализатор родительского класса
self.label = gtk.Label(label) # Создаём текстовую метку с нужной подписью
self.entry = gtk.Entry() # И поле для ввода текста
self.pack_start(self.label, expand=False) # Добавляем label в создаваемый виджет
self.pack_start(self.entry, expand=True) # Поле для ввода - туда же

Теперь можно дописывать методы по собственному усмотрению. Например, логично было бы видеть методы set_text и get_text:

  def get_text(self):
return self.entry.get_text()
  def set_text(self,text):
self.entry.set_text(text)

При желании можно добавить, например, get_label и set_label. Пример использования нашего виджета:

...
entry = LabeledEntry("Enter some text")
...

Таким образом, наследуясь от HBox или VBox, можно создавать виджеты, состоящие из нескольких готовых. Но иногда нужны виджеты, внешне не похожие ни на один из стандартных. И вот тогда выручает то, что все виджеты gtk отрисовываются с помощью Cairo, который имеет весьма простой API.

API этот имеет много общего со многими другими рисовальными API. Прежде всего, нужно получить контекст Cairo - объект, содержащий состояние изображения. Далее для собственно рисования вызываются методы этого объекта. Наиболее часто используемые:

  • cr.move_to(x,y) - переместить графический указатель в нужную точку холста,

  • cr.line_to(x,y) - провести линию от положения указателя до данной точки (указатель сдвинется в указанную точку),

  • cr.path_close() - делает текущую линию замкнутой,

  • cr.rectangle(x,y,w,h) - рисует прямоугольник; задаются координаты левого верхнего угла и размеры,

  • cr.set_source_rgb(r,g,b) - выбрать цвет для рисования; компоненты r,g,b измеряются от 0 до 1,

  • cr.stroke() - нарисовать контур текущей линии (выбранным цветом),

  • cr.fill() - закрасить текущую линию.

Координаты измеряются как обычно - от левого верхнего угла вправо и вниз, в пикселах.

Пусть нам, скажем, нужен виджет, который будет отображать простейшие линейные диаграммы. Должна быть возможность добавлять в него данные, а он должен соответственно перерисовывать диаграмму. Такие виджеты удобнее всего наследовать от gtk.DrawingArea:

  class Diagram(gtk.DrawingArea):

Сам виджет DrawingArea выглядит как белый прямоугольник. И на нём, в соответствии с названием, можно рисовать. Пока сделаем инициализацию нашего виджета:

  def __init__(self,max=10,color=(0.8,0.8,0.6)):
gtk.DrawingArea.__init__(self)
self.data = [1] # Это будут данные, отображаемые виджетом
self.max = max # Сколько максимум данных будет рисовать виджет
self.color = color # Цвет диаграммы
# Вот это, можно сказать, самое главное: привязываем рисующую процедуру к событию перерисовки виджета
self.connect('expose-event', self.on_expose)

Определяем собственно метод, который будет отрисовывать виджет:

  def on_expose(self, widget, event):

В аргументе widget передаётся сам виджет. Первое, что нам от него нужно - это размеры и положение:

    x,y, width,height,_ = widget.window.get_geometry()

Кроме того, нам понадобится контекст Cairo:

    cr = widget.window.cairo_create()

Вычислим некоторые размеры:

    xpad = 0.03*self.width           # Поля по горизонтали
ypad = 0.07*self.height # И по вертикали
w = float(self.width-2*xpad) # Ширина 'рабочей' части виджета
h = float(self.height-2*ypad) # и высота
M = max(self.data) # Максимум данных - он нужен, чтобы выставить масштаб по оси Y
n = len(self.data) # Количество данных
    cr.rectangle(0,0,self.width,self.height)   # Обозначаем прямоугольник, закрывающий весь виджет
cr.set_source_rgb(1,1,1) # Выбираем белый цвет
cr.fill() # Закрашиваем наш прямоугольник - это будет фон
    cr.move_to(xpad, ypad+h-h*float(self.data[0])/M)  # Ставим указатель в верхний левый угол будущей диаграммы
for x,y in enumerate(self.data[1:]): # Пробегаемся по всем данным
cr.line_to(xpad+w*float(x+1)/(n-1), ypad+h-h*float(y)/M) # Проводим очередной отрезок ломанной
cr.line_to(xpad+w, ypad+h) # Проводим правую границу диаграммы
cr.line_to(xpad,ypad+h) # Теперь нижнюю границу
cr.close_path() # Замыкаем ломанную - это проведёт левую границу диаграммы
cr.set_source_rgb(*self.color) # Выбираем цвет
cr.fill() # Закрашиваем ломанную

Этот метод будет вызываться каждый раз, когда нужно перерисовать виджет. Конечно, стоит иметь ввиду, что если он будет выполняться долго - перерисовка виджета будет тормозить. Так что вычислений и циклов в нём должно быть минимум. Всё, что можно, следует вычислять заранее, или кэшировать.

Ну и допишем метод для добавления данных в диаграмму:

def accept(self,n):
if len(self.data) == self.max:
del self.data[0] # Если данных слишком много - забываем самое старое значение
self.data.append(float(n)) # Добавляем число в список
self.queue_draw() # Этот вызов заставит виджет перерисоваться, т.е. лишний раз вызовет on_expose().

Пример использования:

...
dg = Diagram(max=20)
...
dg.accept(10)
dg.accept(20)
...

понедельник, Ноябрь 10, 2008

Typewriter-like раскладки

По совету http://vonderer.blogspot.com/, решил попробовать использовать для русского языка раскладку пишущей машинки. Главное преимущество (для меня) - в том, что знаки препинания обычно в тексте встречаются гораздо чаще, чем цифры, а в typewriter набирать их становится проще. Заодно точка и запятая получают по отдельной клавише, и буква Ё - более удобное место. Вобщем, действительно, удобно. Правда, на привыкание ушло около недели.

Но кроме русской раскладки есть ещё и английская. Для неё в X-ах не предусмотрено tyewriter-варианта, а хочется, потому что на переключение режима в мозгах требуется слишком много времени (цифры набирать то с шифтом, то без, и знаки препинания скачут по всей клавиатуре). Раскладка Дворака (у которой есть вариант с цифрами на верхнем уровне) - слишком другая, а я не так много набираю англоязычных текстов, чтобы изучать совсем новую раскладку (да ещё и надписи на клавишах будут мешать). Вобщем, сделал я себе typewriter-вариант английской раскладки. Выглядит это так:



Соответствующий код (/usr/share/X11/xkb/symbols/ustw):

partial alphanumeric_keys
xkb_symbols "typewriter" {
include "us(basic)"
name[Group1]= "US - Typewriter";
key <AE01> { [exclam, 1 ] };
key <AE02> { [numbersign,2 ] };
key <AE03> { [slash, 3 ] };
key <AE04> { [semicolon, 4 ] };
key <AE05> { [colon, 5 ] };
key <AE06> { [comma, 6 ] };
key <AE07> { [period, 7 ] };
key <AE08> { [asterisk, 8 ] };
key <AE09> { [question, 9 ] };
key <AE10> { [percent, 0 ] };
key <BKSL> { [parenleft, parenright ] };

key <AC10> { [at, ampersand ] };
key <AB08> { [asciicircum, less ] };
key <AB09> { [dollar, greater ] };
key <AB10> { [bar, backslash ] };
};


Кроме того, ещё с давних пор я использую CapsLock как специальный модификатор, превращающий некоторые буквенные клавиши в стрелки итп. Сейчас ещё захотелось на Shift-Caps повесить переключение такого режима (чтоб в браузере тексты читать, листая кнопками j/k, итп). И ещё захотелось временный переключатель из русской раскладки в английскую - иногда >/< или ещё чего набрать быстро. И, раз уж пошла такая пьянка, чтоб можно было греческие буквы побыстрее набирать (временный переключатель в греческую раскладку) (правда, я не верю, что греки пользуются фонетической раскладкой, которая в иксах под именем gr, ну да это их проблемы).

Итак, текущие мои настройки, если кому интересно.

/usr/share/X11/xkb/symbols/addkeys - мои раскладки:


partial alphanumeric_keys
xkb_symbols "en" {
include "ustw"
name[Group1]= "US - Additional";
key.type[group1]="FOUR_LEVEL";
key <AC01> { [ a, A, Home, Home ] };
key <AD03> { [ e, E, End, End ] };
key <AC05> { [ g, G, Home, End ] };
key <AC06> { [ h, H, Left, Left ] };
key <AC07> { [ j, J, Down, Down ] };
key <AC08> { [ k, K, Up, Up ] };
key <AC09> { [ l, L, Right, Right ] };
key <AC03> { [ d, D, Delete, Delete ] };
key <AD10> { [ p, P, XF86ScrollUp, XF86ScrollUp ] };
key <AB06> { [ n, N, XF86ScrollDown, XF86ScrollDown ] };
include "addkeys(caps_switch)"
};

partial alphanumeric_keys
xkb_symbols "ru" {
include "ru(typewriter)"
name[Group1]= "Russia - Additional";
key.type[group1]="FOUR_LEVEL";
key <AC01> { [ Cyrillic_ef, Cyrillic_EF, Home, Home ] };
key <AD03> { [ Cyrillic_u, Cyrillic_U, End, End ] };
key <AC05> { [ Cyrillic_pe, Cyrillic_PE, Home, End ] };
key <AC06> { [ Cyrillic_er, Cyrillic_ER, Left, Left ] };
key <AC07> { [ Cyrillic_o, Cyrillic_O, Down, Down ] };
key <AC08> { [ Cyrillic_el, Cyrillic_EL, Up, Up ] };
key <AC09> { [ Cyrillic_de, Cyrillic_DE, Right, Right ] };
key <AE11> { [ minus, underscore, emdash, hyphen ] };
key <AE12> { [ equal, plus, notequal, plusminus ] };
key <AC03> { [ Cyrillic_ve, Cyrillic_VE, Delete, Delete ] };
key <AD11> { [ Cyrillic_ha, Cyrillic_HA, bracketleft, braceleft ] };
key <AD12> { [Cyrillic_hardsign,Cyrillic_HARDSIGN, bracketright, braceright ] };
key <AD10> { [ Cyrillic_ze, Cyrillic_ZE, XF86ScrollUp, XF86ScrollUp ] };
key <AB06> { [ Cyrillic_te, Cyrillic_TE, XF86ScrollDown, XF86ScrollDown ] };
include "addkeys(caps_switch)"
};

partial alphanumeric_keys
xkb_symbols "gr" {
include "gr"
name[Group1]= "Greek - Additional";
key.type[group1]="FOUR_LEVEL";
key <AC01> { [ Greek_alpha, Greek_ALPHA, Home, Home ] };
key <AD03> { [ Greek_epsilon, Greek_EPSILON, End, End ] };
key <AC05> { [ Greek_gamma, Greek_GAMMA, Home, End ] };
key <AC06> { [ Greek_eta, Greek_ETA, Left, Left ] };
key <AC07> { [ Greek_xi, Greek_XI, Down, Down ] };
key <AC08> { [ Greek_kappa, Greek_KAPPA, Up, Up ] };
key <AC09> { [ Greek_lambda, Greek_LAMBDA, Right, Right ] };
key <AC03> { [ Greek_delta, Greek_DELTA, Delete, Delete ] };
include "addkeys(caps_switch)"
};

xkb_symbols "caps_switch" {
key <CAPS> {
type[Group1]="ONE_LEVEL",
symbols[Group1] = [ ISO_Level3_Shift ]
};
modifier_map Mod5 { ISO_Level3_Shift };

replace key <II65> {
type[Group1]="ONE_LEVEL",
actions[Group1] = [ SetGroup(group=3) ],
actions[Group2] = [ SetGroup(group=3) ],
actions[Group3] = [ ],
actions[Group4] = [ SetGroup(group=3) ]
};

replace key <I21> {
type[Group1]="ONE_LEVEL",
type[Group2]="ONE_LEVEL",
actions[Group1] = [ SetGroup(group=1) ],
actions[Group2] = [ SetGroup(group=1) ],
actions[Group3] = [ SetGroup(group=1) ],
actions[Group4] = [ ]
};

replace key <RCTL> {
actions[Group1] = [ SetGroup(group=2) ],
actions[Group2] = [ SetGroup(group=1) ],
actions[Group3] = [ SetGroup(group=1) ],
actions[Group4] = [ ],
locks = yes
};
};


Ну и в /etc/X11/xorg.conf:


Option "XkbLayout" "addkeys(en),addkeys(ru),gr"
Option "XkbOptions" "grp_led:caps,compose:ralt"



Рус/лат переключается правым Ctrl, индикация лампочкой Caps. Временный переключатель в английскую раскладку на клавише <I21> (у меня она рядом с левым Ctrl). На клавише <II65> (у меня над <I21>) - временный переключатель в третью раскладку (греческие буквы иногда набрать). По Caps+буква - некоторые спецклавиши: Caps-hjkl - стрелки, Caps-a - Home, Caps-e - End, Caps-g - Home, Caps-G - End. На правом Alt - Compose.

четверг, Июнь 19, 2008

Deployment и Git

Сперва - что такое deployment?

Буквальный перевод - развертывание. Речь идет о том, чтобы заставить код, написанный разработчиком в своей песочнице, заставить работать в реальных условиях на "боевых серверах". И вот во что это выливается даже в случае с одним разработчиком:

Мой нынешний проект я разрабатываю/тестирую на своем домашнем сервере (dev-сервер, это называется). А недавно выложил на хостинг - это, так сказать, production-сервер (ну, на самом деле, это пока что разновидность тестирования). И тут появляются некоторые ньюансы:

  • на dev- и production- серверах нужны разные настройки кода (например, разные пароли для коннекта к БД);

  • на dev-сервере я продолжаю разработку, добавляю новые фичи и пр. Хотелось бы, чтобы новые фичи и в production-варианте появлялись;

  • на production-сервере вылезают некоторые баги, которые по разным причинам на dev не вылезали, их приходится фиксить. Хотелось бы, чтобы эти же баги были пофиксены и в dev-версии;

  • Т.к. код еще не доведен "до кондиции", при переносе выясняется, что кое-где он написан неуниверсально (зависит от специфики того места, где работает). Для работы production-сервере его приходится после выкладывания обобщать. Конечно, нужно, чтобы и в dev-версии он был не менее общим;

  • Некоторые изменения, сделанные в коде production-версии, всё-таки специфичны именно для данного конкретного хостинга. Они в версии на dev-сервере мне совсем не нужны;

  • Кроме всего этого, я хочу держать актуальную версию кода в публичном месте, чтобы любой желающий мог его скачать.

Несложно догадаться, что при попытке выполнять все эти требования "вручную" - очень быстро запутаешься в трех соснах (то бишь, версиях). Для упрощения deployment-а существует довольно много всяких разных решений. Одно из возможных - использовать Git.

Предположим, код на dev-сервере уже под контролем git. Тогда для разворачивания проекта делаем:

user@production.server:/project$ git init
user@dev.server:~/project$ git push ssh://production.server/project master

Собственно, теперь на production.server в директории /project/ имеем копию кода с dev-сервера. Далее:

user@production.server:/project$ git branch production
user@production.server:/project$ git checkout production

Это мы создали новую ветвь репозитория и переключились в нее. Теперь изменяем настройки, тестируем, фиксим баги… не забываем после каждого логически завершенного изменения делать "git commit -a" - фиксировать изменения в репозитории.

При внесении изменений в код на dev-сервере их, конечно, тоже фиксируем. Когда захотим обновить версию на production, делаем:

user@dev.server:~/project$ git push
user@production.server:/project$ git merge master

— вливаем изменения в ветви master в production. Если нужно применить только последнее изменение в master (последний коммит), вместо git merge делаем git cherry-pick.

Чтобы применять нужные изменения, сделанные на production, к dev-версии, делаем:

user@dev.server:~/project$ git remote add prod ssh://production.server/project

— создаем ссылку на удаленный репозиторий,

user@dev.server:~/project$ git fetch prod/production

— получаем код из ветви production,

user@dev.server:~/project$ git cherry-pick prod/production

— это если нужно применить последнее изменение в production, или

user@dev.server:~/project$ git cherry-pick идентификатор-коммита

— если нужно применить произвольный коммит.