Saturday, October 19, 2013

How to retrieve process info by its port on Solaris

I've never got any problems with finding process info by its port number. Every man who worked with Linux knows about netstat:
netstat -p |grep :8010

tcp    0    0*    LISTEN    2122/kopete     
udp    0    0*              2122/kopet
or lsof:
lsof -i :8010 | grep LISTEN

kopete  2122 mikhail 17u  IPv4 17717 0t0  TCP *:8010 (LISTEN)
Somebody even knows about fuser:
fuser -n tcp 8010

8010/tcp:  2122
But all these commands do not matter under Solaris:

All we have (on Solaris 5.10) is a /proc filesystem (with list of current processes) and Solaris utility pfiles, which allows us to see files associated with a process. So, the only way to solve this task is a little shell script:

for pid in `ls /proc`
pfiles $pid | grep AF_INET | grep $1
if [ $? -eq 0 ]; then
    echo $pid
Yes, it looks terrible. Moreover I really don't understand why I must modificate a proprietary operation system... But let's get back to process info.

Once we have a process PID, we can retrieve any info about it:
/usr/ucb/ps auxvv | grep 2122

mikhail   2122  0.1  0.5 148096 35152 ?   Sl   19:58   0:02 /usr/bin/kopete -session XXX
ls -l /proc/2122/exe

lrwxrwxrwx 1 mikhail mikhail 0 Oct 19 19:58 /proc/2122/exe -> /usr/bin/kopete


Saturday, August 31, 2013

Unit tests and Class loaders

Let's assume that we have a third-party library. This library is written with a lot of errors. The most terrible of them is an possible exception, which can be thrown in a static constructor of one of key library's classes :( Of cause, it is very sad... but we need to go further - we need to write unit tests for this library! :)

I know that it is stupid and useless... but let's look on it from academic point of view.

First of all, let's take a look on that tricky class:

public class Problem {

    static {
        InputStream is = ClassLoader.getSystemResourceAsStream("problem.cfg");
        if (null == is) {
            throw new RuntimeException("Config not found");
So, what will happen when configuration file will not be found in a classpath? Yes, its obvious that RuntimeException will be thrown :) But what then? Then classloader will throw an ExceptionInInitializerError. But this is not all. Since classloader fails to load class it marks that class as "bad" and don't take any attempts to load it in future.

Hence, if we'll try to initialize Problem class without it's config, we get ExceptionInInitializerError at first time. If we repeat our actions we'll get NoClassDefFoundError.

Cool. But what if we want to write unit test for this class, what assumes several attempts of class initialization? And please, don't ask me why - we just want to do so :)

We can't unload class from Java, except of case of garbage collection (which is uncontrollable for us). Also we can run each unit test in it's own JVM, but this approach requires a big changes in unit test framework. So, the only way that we have is launch each unit test with it's own classloader.

Class loaders

Let's distract for a few minutes and recall what is classloaders and how they work. I don't want to write "yet another" article about classloaders, so I just list main facts:
  • Every class in runtime enviroment must be loaded by some class loader;
  • Most of classes are loaded by demand;
  • There are three types of class loaders:
    • bootstrap - loads base java classes (rt.jar, i18n.jar, etc), built in JVM, can be configured by JAVA_OPTS -Xbootclasspath;
    • extension classloader - loads classes from $JAVA_HOME/lib/ext, implemented by sun.misc.Launcher$ExtClassLoader;
    • system classloader - loads classes from classpath, implemented by sun.misc.Launcher$AppClassLoader;
  • Classloaders are built in hierarchy (as listed above). When classloader needs to load class he asks his parent first;
  • Apart of this hierarchy there are two other kind of classloaders:
    • current classloader - classloader of current class, which is used by default for class loading at runtime (Class.forName, ClassLoader.loadClass or simple first time class declaration). Can be retrieved by Clazz.class.getClassLoader() or this.getClass().getClassLoader();
    • context classloader - classloader for current thread. Can be used with Thread.getContextClassLoader()/Thread.setContextClassLoader() methods.
  • You can define your own classloaders (as inheritors of hierarchy or not) to provide special ways of loading (network, archives) or tricky behavior (as in our case);
Here is classloaders hierarchy diagram (stolen from some other blog):

So, if we need to create independent classloaders for our issue we should do next:

public class TestClassLoader extends URLClassLoader {
    public TestClassLoader() {
        super(((URLClassLoader) getSystemClassLoader()).getURLs(), null);
Notice, if second parameter equals null in super constructor, that our class loader will not have a parent. In this case, new classloader will have it's own classes collection. This means that class A loaded with one instance of TestClassLoader will not be equals (and can't be casted) to class A loaded with another instance of TestClassLoader.

If we want to have classloaders with common set of classes, we must include them in current class loaders hierarchy:

public class TestClassLoader extends URLClassLoader {
 public TestClassLoader() {
Every instance of this classloader will load it's own classes, if they don't already loaded by parent classloader (when class loader needs to load class he asks his parent first). Fortunately, you can override loading logic for specific classes:

public class TestClassLoader extends URLClassLoader {
    public TestClassLoader() {
        super(((URLClassLoader) getSystemClassLoader()).getURLs());

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        if (name.startsWith("example.Problem")) {
            return super.findClass(name);

        return super.loadClass(name);
But remember, if parent classloader already loaded your class, you can't cast class from you classloader like this:

ClassLoader testClassLoader = new TestClassLoader();
Problem problem = (Problem) Class.forName(Problem.class.getName(), true, testClassLoader).newInstance();
Because now you have two different classes from different classloaders. If you want to call some method of object of loaded class you should use reflection:

ClassLoader testClassLoader = new TestClassLoader();
Class<?>  problemClass = Class.forName(Problem.class.getName(), true, testClassLoader);
Object obj = problemClass.newInstance();

Method method = problemClass.getMethod("methodName");
As for Unit tests, you can implement your own runner to run every test class with it's own class loader:

public class SeparateClassloaderTestRunner extends BlockJUnit4ClassRunner {

    public SeparateClassloaderTestRunner(Class<?> clazz) throws InitializationError {

    private static Class<?> getTestClass(Class<?> clazz) throws InitializationError {
        try {
            return Class.forName(clazz.getName(), truenew TestClassLoader());
        } catch (ClassNotFoundException e) {
            throw new InitializationError(e);

Class path

OK, now we know how to run several tests with different class loaders. But how can we change Problem class behaviour? If this class would use Problem.class.getSystemResourceAsStream() method we could manipulate it's classpath during classloader creation, controlling set of URL that is passed into constructor. Also it wouldn't be a problem if it used context loader, which can be easy changed with Thread.setContextClassLoader() method.
Unfortunately, it uses ClassLoader.getSystemResourceAsStream() method. This means that we should change current classloader's classpath..

Strictly speaking, we can't change classpath of current classloaders. But there is one dirty trick :)

URL url = new URL(this.getClass().getResource("/").toString() + "path/to/problem/config/");
URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader

Class<?> urlClass = URLClassLoader.class;
Method method = urlClass.getDeclaredMethod("addURL"new Class[] { URL.class });
method.invoke(urlClassLoader, new Object[] { url });
This approach doesn't allow us to remove objects from classpath... but adding will be enough for our purpose.

Putting all together

Now we know that we can create instances of Porblem class as many time as we want. Though, we have one restriction: test without config file in classpath should runs first. That's because we can't remove element from classpath after we added it. And that's why we can't use JUnit runner.

So, let's write a simple test:

public class ProblemTest {

    public void testCommon() throws Exception {


    public void testWithoutConfig() throws Exception {
        ClassLoader testClassLoader = new TestClassLoader();

        try {
            Class<?> problemClass = Class.forName(Problem.class.getName(),
                    true, testClassLoader);

            fail("Problem class can't be created without config");

        } catch (Throwable e) {

    public void testWithConfig() throws Exception {
        ClassLoader testClassLoader = new TestClassLoader();

        URL url = new URL(this.getClass().getResource("/").toString() + "cfg/");
        URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader

        Class<?> urlClass = URLClassLoader.class;
        Method method = urlClass.getDeclaredMethod("addURL",
                new Class[] { URL.class });
        method.invoke(urlClassLoader, new Object[] { url });

        Class<?> problemClass = Class.forName(Problem.class.getName(), true,

    private class TestClassLoader extends URLClassLoader {
        public TestClassLoader() {
            super(((URLClassLoader) getSystemClassLoader()).getURLs());

        public Class<?> loadClass(String name) throws ClassNotFoundException {
            if (name.startsWith("")) {
                return super.findClass(name);

            return super.loadClass(name);


Code in this article is highlighted with CodeHtmler.


Sunday, March 31, 2013

Codefest 2013

И так, на этих выходных (30-31 марта) прошла ставшая уже ежегодной IT-конференция Codefest. В этой статье я вкратце поделюсь полезной информацией и впечатлениями, которые мне удалось вынести с конференции.


Первым делом начнём с докладов. Скажу честно, они были разными: скучные и интересные, полезные и бесполезные, русскоязычные и англоязычные. Трэша от Аксёнова на этот раз не было... а жаль :( Но обо всём по порядку.

Как подружить несколько языков программирования на вашем Backend'e

Рассказ от разработчиков 2GIS. Пожалуй, самый полезный для меня за этот Codefest.

Речь шла об интеграции нескольких систем, используемых в компании, написанных на разных языках. В качестве примера рассматривалась библиотека написанная на C++, используемая в нескольких проектах: Web, Mobile, плюс что-то ещё.

Способов интеграции приводилось несколько:

  • Fork/ Exec: вызов внешней программы из PHP;
    • просто реализовать;
    • большое кол-во накладных расходов;
  • Модули / Extension для PHP;
    • сложно реализовать;
    • сложно интегрировать;
  • Отдельная служба + API для доступа к ней;
    • необходиость разработки протокола и его реализации на всех платформах (плохо);
    • можно взять распространённый протокол (PROFIT!);

Опыт 2GIS привёл их к последнему подходу. В качестве протоколов рассматривались thrift/protobuff/message pack. Выиграл Thrift.

Фичи Thrift:

  • полноценная поддержка rpc (а не только сериализации); 
  • можно определить свой транспортный данных протокол (tcp, http); 
  • можно определить свой передачи данных протокол (binary, json, и т.д.); 
  • поддержка версионирования (через id поля);
При испоьзовании подхода, когда каждая библиотека представляет собой демона/службу, работа с которой идёт по thrift-протоколу, получаем типичную SOA-арихитектуру приложения.

Thrift используется в Facebook и Evernote.

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

Использование систем виртуализации в Web

Рассказ про как автор работал с ZFS и на какие грабли наступал. Ничего особо интересного, лишь частное мнение и порой не самый удачный опыт.

Порадовало большое количество вопросов от сотрудников Parallels в виду небольшого вброса в сторону OpenVZ, сделанного автором.

Cobbler for Developers

Видимо первый IT-доклад в Сибири на английском языке. Радует, что мировая практика докатилась и до Нск.

Рассказ про сложную судьбу инженера RedHat, которому снова и снова приходилось разворачивать ПО на сотнях и сотнях машин. Всё сводится к связке Cobbler/Chief/Puppet.

ECMAScript 6

Доклад посвящён новой версии JavaScript (ECMA6) и её фичам. По сути сводился к зачитыванию roadmap'а / спецификации с примерами.

В отличие от предыдущего докладчика, для Алекса английский судя по всему не был родным языком. В виду чего понимать его речь была намного чётче и понимать её было намного легче.

Высокопроизводительные визуализации данных в браузере

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

Из доклада стоит выделить следующие вещи:

  • алгоритмы уменьшения количества числа точек при отрисовке траекторий;
  • кластеризация (группировка) объектов для уменьшения их количества;
  • тяжелые расчёты в отдельном Worker'e;
  • asm.js;
  • использование SVG / Canvas;
  • WebGL для 3D-визуализаций;

NoSQL через MySQL + HandlerSocket

Ещё раз о том, что не стоит кешировать всё подряд в memcache-подобных хранилищах. Некоторые вещи (например, простые запросы по ключу) проще выбирать из исходной БД на более низком уровне через HandlerSocket-интерфейс, минуя разбор SQL, построение плана, работу с транзакциями и т.д.

При таком подходе у нас отсутствуют проблемы с необходимостью поддержания консинстентности между СУБД и кешем.
>HandlerSocket подключается плагином к InnoDB / XtraDB. Таким образом мы получаем два интерфейса для доступа к БД: sql-клиент и HandlerSocket клиент.

Должен заметить, что сам протокол работы с HandlerSocket является бинарным и... довольно неудобным. Выборки осуществляются на основе индексов, что вполне логично.

Рецепты успеха:

  • шардинг при размере таблицы > 10M рядов; (не смотря, что сложность поиска по B-tree индексам log(n), опыт докладчика показывает, что при количестве записей > 10М сложность возрастает до квадратичной); 
  • persistence коннекты; 
  • hash-индексы (*);

На первом пункте я бы хотел остановится подробней. Дело в том, что у меня тоже есть один батхёрт в виде таблицы на 30-50М записей с большим числом записей (300К/сутки), относительно небольшим числом чтений (10К/сутки) при полным отсутствием изменений/удалений. Время от времени таблица архивируется, для сокращения числа записей. Изменение скорости выборки данных до и после архивации как раз соотносится с log(n). И да, речь в моём случае о Sybase... но я несколько удивлён, что докладчик обнаружил подобную проблему на MySQL.

И последнее, HandlerSocket (а не MongoDB и Redis) стоит использовать в виду фич MySQL - репликация и т.д. 

Пути миграции перелётных данных

Очень весёлый и забавный доклад. В нескольких словах описать его следующим тезисом: "Построение отчётности это не просто ценный мех, а Data WareHouse, Business Intellegence и Big Data Mining". 

Открытие офиса удалённой разработки

Рассказ про то, как ребята из 2Gis открывали офис в Киеве.

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

Проект, который должен передаваться в удалённый офис, должен быть максимально независим от других проектов компании.

Первым делом выбирался город/страна на основе анализа ИТ-рынка (наличие университетов, компаний, мероприятий). Далее была сформирована команда "десанта" из сотрудника, проработавшего в компании долгое время (хорошо знающего компанию) и человека из целевого города (нанятого по объявлению, знакомого с местными особенностями - например, в/на Украине ЗП и аренда платится в гривнах, а фиксируется в долларах).

Далее ребята занялись рекламой бренда 2Gis, для повышения узнаваемости компании среди разработчиков. После того, как успех был достигнут пришло время для собеседований. Изначально пул кандидатов собеседовался по Skype из Нск, потом лично на месте. Также проводилась работа среди студентов местных ВУЗов.

Первый код в продакшн был получен через 4 месяца.

Примечательно, что в итоге стоимость разработчиков в Киеве оказалась даже немного выше, чем в Нск.

Управление памятью в Java

Довольно интересный технический доклад о работе с памятью в Java.

При оптимизации GC можно минимизировать только два из трёх параметров:
  • throughtput (количество вычислительных ресурсов);
  • предсказуемость (количество времени, которое придётся затратить);
  • footprint (объём используемой памяти);

Типы ссылок в Java:
  • Strong - классическая ссылка на объект;
  • Soft - объекты могут быть удалены при OutOfMemory;
  • Weak - GC может удалить объект в любой момент;
  • Phanton - получение нотификации о смерти объекта (с помощью специальной очереди, для замены финализаторов);

Получить содержимое кучи можно с помощью следующих утилит:
  • jmap (классическая утилита);
  • jhat(подымает web-сервер для отображения данных);

Зачем вам нужна Clojure

Обзорный доклад о языке программирования Clojure.

Как мы сделали Яндекс.Гардероб и не ослепли

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

Каким не должен быть лидер технологической компании

Крайне странный доклад. Больше похож на самопиар

Собственно советы: 
  • Постоянно контролируйте работу своих сотрудников;
  • Контролируйте все коммуникации;
  • Рассказывайте как можно меньше информации своей команде;
  • Не обращайте внимания на коммуникационные проблемы;
  • Скрывайте негатив;
  • Больше хвалите себя в глазах других;
  • Количество, но не качество;
  • Мотивируйте сотрудников только деньгами;
  • Вводите как можно больше правил - должно быть меньше свободы;
  • Не имеет смысл инвестировать в сотрудников;
  • Не хвалите сотрудников за их успехи;
  • Присваивайте все достижения себе; 

Ну и да, в всё наоборот :)

Мониторинг API

Суть доклада сводится к тому, что любой сервис стоит мониторить. Это позволяет повысить качество и решить множество проблем. (с) Кэп.

Стандартные решения типа Munin/Zabbix ребятам не подошли, поэтому они написали свою систему мониторинга. Russain way :)

MyBatis & Hibernate, давайте жить дружно!

Докладчик предлагает использовать в одном проекте несколько ORM-движков: Hibernate - где выборки несложны, MyBatis - где нужны сложные запросы.

Категорически с ним не согласен - ORM должен быть один. Только один!

С помощью Hibernate - можно решить любые проблемы. Главное понимать, что он не является серебряной пулей. Сложные запросы можно и нужно писать на HSQL или сыром SQL.

Добавляя второй ORM к Hibernate мы увеличиваем количество соединений к БД, делаем невозможным использование таких фич как кэш Hibernate и т.д.

Кроме того концепция iBatis, при которой SQL хранится в одном месте, а код в другом кажется мне крайне неудобной.

Хочу отметить, что сам докладчик выступил отлично и обидеть его декламированием своей позиции я не хотел. Просто я правда с ним очень не согласен :)



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

Однако, всегда есть то, что можно было бы улучшить. Прошу понять правильно - я не в коем случае не критикую, просто хочу дать feedback :)

В первую очередь - это питание. Радиусе 5км от экспоцентра в выходные дни поесть реально не где. Вообще. Кругом степь... и то без травы или прочей растительности :(
Можно отдать 1К за два самолётных обеда... но видимо 3 года в обнимку с ипотекой сделали меня через чур жадным и мелочным.

Во-вторых, WiFi. Если честно, WiFi не работает толком ни на одной конференции. Ни в Нск, ни в Москве. Видимо проблема в самой технологии и технических ограничениях оборудования. Человек, который решит проблемы организации массового беспроводного доступа в интернет на ограниченной территории в рамках приемлемого бюджета получит от меня пирожок... с клюквой :)


Saturday, January 12, 2013

Декомпиляция Android-приложений

В этой статье заметке я хочу рассмотреть два способа "разбора" (reverse engineering'a) Android-приложений.

И так, как мы все знаем, приложения под Android распространяются в виде apk-файлов. Последние в свою очередь являются обычным архивом с скомпилированным кодом (в dex-формате, используемым реализацией JVM для Android - Dalvik) и преобразованными для внутреннего использования ресурсами. Другими словами - "нельзя просто так взять и посмотреть исходники Android-приложения". Однако, способы всё же есть.

Android APK Tool

Первый способ - воспользоваться утилитой apktool. С её помощью можно без труда разобрать, внести некоторые правки и собрать обратно приложение:
apktool decode myapp.apk myapp
apktool build myapp myapp.apk
Однако, есть одно "но". После разбора приложения вы получите не Java-файлы с кодом, а файлы с набором мета-инструкций JVM в формате проекта SMALI. Не смотря на то, что проект SMALI считает себя "human-editable" и "human-readable" - правка байткода есть удовольствие для истинных эстетов. Поэтому данный способ подходит больше для того, что бы выдернуть что-нибудь из ресурсов проекта.

Dex2Jar and JD

Если же ваша цель - исходный код (пусть и восстановленный средствами reverse engineering'a), вам поможет утилита Dex2Jar, которая без труда сделает из вашего apk-файла обычный jar-архив, преобразовав ресурсы и заменив исполняемые файлы Dalvika на обычные классы для JVM. myapp.apk
Теперь, что бы посмотреть код, достаточно воспользоваться любым java-декомпилятором. Например проектом JD-GUI, выгодно отличающимся наличием графического интерфейса.
jd-gui myapp.jar


Wednesday, January 2, 2013

Нагрузочное тестирование ВидеоСервера

Пару месяцев назад передо мной встала задача - выяснить, сколько клиентов одновременно может вывезти один видеосервер на основе VLC. В качетсве протоколов вещания предлагалось использовать HLS или HTTP Streaming. Сразу хочу оговориться, что специалистом в тестировании (тем более нагрузочном) я не являюсь. Однако, полученный мною опыт кажется мне весьма интересным.


Предполагаемая нагрузка

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

Эмуляция нагрузки

Основной особенностью тестирования видеосервера является тот факт, что при проигрывании видео нагрузка ложится не только на сервер, но и на клиент (а так же канал между ними). Отсюда путей для тестирования мне видится два:
  • полная эмуляция клиента (запуск vlc-плеера);
  • частичная эмуляция клиента (скачивание файла через wget);
В любом случае ресурсов одной тестовой машины будет недостаточно для эмуляции рабочей нагрузки. Конкретные цифры зависят от мощности железа и пропускной способности канала между тестовым клиентом и сервером. Однако, в виду предполагаемой нагрузки (см. выше) полная эмуляция клиента технически невозможна, так как потребует неопраданно большого количества машин. Создание же своего маленького botnet'a в благородных целях тестирования в рамках данной статьи мы рассматривать не будем :)

Распределённое тестирование

Понятно, что для тестирования нам потребуется много машин. Другими словами, нагрузочное тестирование должно быть распределённым.

В качестве framework'a для тестирования предлагается использовать JMeter, обладающий всей необходимой нам функциональностью. Идея заключается в том, что бы согласно инструкции развернуть несколько клиентских instance'ов на тестовых машине(ах) (с гигабитным каналом до сервера) и запускать требуемые нам тесты распределённо. Отдельно стоит обратить внимание на то, что на все клиентские машины придётся установить необходимое для тестов ПО: wget или vlc.

Сценарий тестирования

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

В случае тестирования с полной эмуляцией сценарии будут сводиться к вызову vlc-плеера (хотя можно использовать и другой, так как у vlc есть некоторые проблемы с проигрыванием HLS):
vlc -Idummy -Vdummy --no-audio
vlc -Idummy -Vdummy --no-audio

В случае тестирования с частичной эмуляцией сценарии будут сводиться к вызову wget:
wget -q --limit-rate=128k -O- >/dev/null
curl -s |grep http |xargs wget -q --limit-rate=128k -O- >/dev/null

Таким образом вся полезная нагрузка создаётся с помощью сторонних программ. JMeter используется как фреймворк для распределения задач между серверами, контроля их выполнения, регулирования количества нагрузки (количество машин/потоков) и сбора отчётности.

Важно! При создании задачи на исполнение внешней программы в JMeter параметры необходимо указывать слитно со значением.

Если нагрузка будет эмулироваться с одной машины, то использование JMeter можно заменить на простой bash-скрипт:
    nohup КОМАНДА &


При тестировании с частичной эмуляцией клиента (с помощью wget) первым шагом необходимо определить минимальную скорость передачи данных, при которой возможно корректное воспроизведение видео, закодированного в используемом нами формате. Другуми словами необходимо определить скорость, с которой отдаётся поток на сервере вещания: клиенты с более высокой скоростью будут простаивать в ожидании данных, клиенты с более низкой скоростью будут корректировать свою скорость, пропуская части видеопотока. С учётом используемых в моём случае настроек кодирования (video bitrate = 1Mbit/s), скорость отдачи видео потока составляет примерно 128 Kb/s.

Во время запуска тестов необходимо мониоторить нагрузку на процессор и сетевую подсистему.

Загрузка процессора вычисляется с помощью команды ps -p PID -o %cpu, так как "Currently, it is the CPU time used divided by the time the process has been running (cputime/realtime ratio), expressed as a percentage. It will not add up to 100% unless you are lucky.(alias pcpu)." В виду вышесказанного после каждого теста vlc перезапускается.

С помощью утилиты iftop контролируется наличие ожидаемого объёма трафика в сетевой подсистеме. Так же с её помощью необходимо следить за тем, что бы канал не забивался полностью - в противном случае тест стоит остановить, резюмировав, что данное количество клиентов не может быть обработано текущей сетевой подсистемой.

Архиважно контролировать доступность и качество видеопотока, переодически подключаясь N+1 клиентом к серверу.

Перед началом тестирования убедитесь, что на сервере отключены все лимиты по ресурсам и открытым файлам (или же они совпадают с боевыми лимитами). В моём случае, к примеру, по началу не удавалось подключить одновременно более 350 клиентов из-за параметра numtcpsock, отвечающего за максимальное количество открытых TCP соединений в OpenVZ.


  • Отключаем сервер VLC и всех клиентов;
  • Перезапускаем сервер VLС и пул клиентов;
  • Мониторим количество клиентов и трафик на сервере;
  • Оцениваем нагрузку на сервере;


Подробно на результатах тестирования я останавливаться не буду, так как пос всё же о методике и подходе. Скажу только, что как и ожидалось, в обоих случаях узким местом стала сетевая подсистема тестового сервера, а не ресурсы CPU. Ну и да, барьер в 1000 пользователей был взят :)