Мониторинг температуры процессора на нескольких машинах одновременно. Часть 2

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

На данный момент у нас есть готовый работающий приёмник данных с клиентов. Осталось написать сам клиент. Сразу оговорюсь, что писать код будем для ОС семейства GNU/Linux, но с возможностью простого и быстрого масштабирования для остальных ОС. Благо, язык обладает для этого всеми средствами.

Это средство заключено в одном простом кодовом слове языка D – слове version. Используется оно в следующем ключе:

void main() {
    version(something) {
        //ваш код
    }
}

Теперь, если мы добавим к команде компиляции параметр -version=something…

    dmd sender.d -version=something

то

 //ваш код

будет сгенерирован или нет автоматически.

void main() {
    //ваш код
}

Вся прелесть слова version в том, что оно может принимать сразу несколько значений и обрабатывать их можно по-разному!

Свою лепту вносит и сам компилятор. При компиляции программы компилятор сам обнаруживает тип ОС, под которой он работает и добавляет к version значения для компиляции платформозависимого кода. При компиляции под Windows к version добавляется значение Windows, под Linux/BSD — Posix и т.д.

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

interface Hardware {
    float[] getTemperature();
}

Массив дробных чисел нужен нам для того, что-бы получать температуру с виртуальных ядер, а не одного.

Теперь опишем класс PosixOS, описывающий взаимодействие с ОС Linux.
В этом семействе ОС информацию о процессоре, как и обо всём остальном, можно узнать из виртуальных файлов, расположенных в специальных директориях. Информация о температуре процессорах/ядрах(по крайней мере в Ubuntu 16.04 ) находится в директории /sys/devices/platform/coretemp.0/hwmon/. Файлы, через которые можно узнать температуры имеют окончание _input(т.е. имена обычно имеют названия temp3_input, temp1_input и др.). Что-бы получать данные со всех ядер, необходимо найти все соответствующие им файлы с этим окончанием. Для этого в Phobos есть функция dirEntries из модуля std.file, находящая все файлы с указанным названием.

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

version(Posix)
{
    class PosixOS : Hardware {
        this() {
            assert(exists(dir), "Directory with temp files is not exists");

            auto files = dirEntries(
                        dir,
                        "*_input",
                        SpanMode.depth,
                        false);

            foreach(d; files) {
                coreFilenames ~= d.name;
            }
        }

        override float[] getTemperature() {
            float[] temp;

            foreach(file; coreFilenames) {
                auto data = to!string(read(file));
                temp ~= to!float(data[0..$-4]);
            }

            return temp;
        }

        enum dir = "/sys/devices/platform/coretemp.0/hwmon/";

        string[] coreFilenames;
    }
}

Далее нам осталось реализовать функцию main. Сразу оговорюсь, что для большего удобства будем устанавливать частоту отправки на стороне клиента, так мы сможем устанавливать для каждого клиента свою частоту.
Передавать IP сервера, имя клиента и частоту отправки будем в качестве аргументов для запуска программы.

void main(string[] args) {
    int durationInMS = 1000;
    string ip = "localhost:2015";
    string name = "Sender";

    auto result = getopt(args,
        "p", "period", &durationInMS,
        "i", "ip", &ip,
        "n", "name of sender", &name);

    if(result.helpWanted) {
        defaultGetoptPrinter("Some info about the program.", result.options);
    }

    writeln("The sending period is set on ", durationInMS, "mseconds");

    //...
}

Создаём подключение к серверу, после чего сразу передаём ему информацию о клиенте:

    //..
    auto address = new InternetAddress(2015);
    address.parse(ip);

    auto listenerSocket = new TcpSocket();

    listenerSocket.connect(address);

    writeln("The connection is established!");

    auto received = listenerSocket.send(name);

    if(received == Socket.ERROR) {
        writeln("Error.");
        listenerSocket.close();
    }
    //..

Следующие строки, как мы говорили ранее, будут генерироваться с оглядкой на переменную version. Пишем под Linux, поэтому воспользуемся идентификатором Posix:

    //..
    version(Posix) {
        auto hard = new PosixOS;
    }
    //..

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

Вообще ключевое слово scope – чуточку запутанная тема. Если говорить достаточно кратко , то тело выражения описывает те действия, которые будут выполняться при выходе из функции при обстоятельствах, описанных в аргументах scope. На данный момент у scope есть три возможных аргумента: exit(выполняется при выходе из функции в любом случае), sucess (выполняется ТОЛЬКО при успешном завершении работы функции) и failure (при выходе из функции в результате ошибки).

Иногда это помогает избегать некоторых побочных эффектов при некорректном или нормальном завершении программы.

    //..
    auto ct = Clock.currTime;

    while(listenerSocket.isAlive()) {
        auto nt = Clock.currTime;

        auto dr = (nt - ct);

        if(dr.total!"msecs" >= durationInMS) {
            ct = Clock.currTime;

            string buffer = to!string(hard.getTemperature());

            received = listenerSocket.send(buffer);

            if(received == Socket.ERROR) {
                writeln("Error");
                listenerSocket.close();
            }
        }
    }

    scope(exit) {
        listenerSocket.close();
    }
    //…

Вот и всё!

Исходный код всего проекта лежит на GitHub.

P.S. В конце хотел бы выразить благодарность Олегу Бахарёву(aquaratixc) за мотивацию моей ленивой задницы для завершения второй части статьи, и кооперации LightHouse Software за возможность публикации моих первых, хоть и скромненьких, статей.

Добавить комментарий