Простой диспетчер задач с веб-интерфейсом, написанный на языке GO для Unix-систем включая Android.
Когда я работал в одной фирме, которая занимается электронными гос-услугами, меня всегда удивляло, что системы, которые мы делали или модернизировали становятся со временем чрезвычайно сложными. Они становились рыхлыми и ненадежными с огромным количеством зависимостей. Подобно красному гиганту, система раздувалась и в любой момент могла сколапсировать.
Развитие систем — не только наращивание функционала, но еще и сокращение и оптимизация систем.
Как мне кажется, в it-сфере происходит то же самое. Взгляните на Windows 8.1, это же жертва идей маркетологов. Помните Torrent-клиент Azureus? Теперь это уже целый медиа-комбайн Vuze.
Мне всегда хотелось написать программу для себя. Что-нибудь простое и не сложное, работающее через веб-интерфес, что-то вроде диспетчера задач top, только работающее через браузер. Очень много систем которые могут выполнять подобного рода функционал, но они из серии:
Поставь PHP + Apache+ MySQL+… еще что-нибудь. Т.е. на мой взгляд излишне «раздутые».
Когда я выбирал язык, я обратил свое внимание на GO. На «хабре» в последнее время очень много о нем заслуженно пишут.
Как только я начал читать книги и статьи о нем, сразу влюбился в этот потрясающий лаконичный и естественный язык. Меня поражало то, на сколько, мнение создателей совпадало с моим
Вот примеры:
- В нем нет шаблонов. Шаблоны это наследие макросов. Решая задачу обобщения алгоритмов и классов, использование шаблонов, на порядок усложняло понимание кода.
- В GO нет наследования. Как нет множественного наследования, так и нету обычного наследования. За место наследования используется более четкий и ясные механизмы встраивания и интерфейсы.
- По умолчанию компилируемый файл статически слинкован. Это означает, что его можно запустить на любой системе, не заботясь о зависимостях.
- Из коробки поддерживается кросскомпиляция.
- Есть Unit-тестирование.
Для получения сведения о процессах и статистике системы wtop использует виртуальную файловую систему proc. Поэтому он будет работать на любой системе, которая ее использует(включая android и… я не уверен Plan9). В качестве backend используется встроенный в go http-server. А в качестве frontend html/java script. Для обмена данными между frontenf и backend используется json-сообщения. Для запуска достаточно запустить исполняемый файл и в браузере перейти по адресу x.x.x.x:9977/index.html
На скриншоте выше видно, что в момент сбора сведений, на телефоне с двухъядерным процессором Texas Instruments 4430, частотой 1.2 ГГц, wtop потребляет около 10% процентов процессорного времени и всего 4,5 мегабайта памяти, что немного. На десктопе, с операционной системой ubuntu — 0,5% процессорного времени и те же 4.5 мегабайта памяти. Если в течении 5 секунд не было запроса от клиента, то он засыпает до тех пор, пока новый json-запрос его не разбудит.
Далее опишу какие конструкции основные моменты используются в коде программы
При получении объекта http.Request, метод ProduceJsonRequest «парсит» тело запроса и создает объект запроса. Который в свою очередь диспечеризуется методом Dispatch:
func (fabric *JsonFabric) ProduceJsonRequest(request *http.Request) (Request, error) {
bodyData, err := ioutil.ReadAll(request.Body)
if err != nil {
stErr := "error: Can't read request body"
log.Println(stErr)
return nil, errors.New(stErr)
}
defer request.Body.Close()
var basicRequest BasicRequest
err = json.Unmarshal(bodyData, &basicRequest)
if err != nil {
stErr := "error: Can't parse basic data"
log.Println(stErr)
return nil, errors.New(stErr)
}
switch basicRequest.Type {
case ServiceStatus:
var serviceStateRequest ServiceStateRequest
err := json.Unmarshal(bodyData, &serviceStateRequest)
if err != nil {
stErr := "error: Can't parse service state request"
log.Println(stErr)
return nil, errors.New("error: Can't parse service state request")
}
return serviceStateRequest, nil
......
......
......
}
return nil, errors.New("error: Unknown request type")
func (requestSelector *RequestSelector) Dispatch(request Request, responseWriter http.ResponseWriter, httpRequest *http.Request) error {
//don't need protect multiple read in different thread
if selector, contains := requestSelector.selectorRequestMap[request.RequestType()]; !contains {
stErr := "error: Usupported message type"
log.Println(stErr)
return errors.New(stErr)
} else {
return selector.Dispatch(request, responseWriter, httpRequest)
}
}
При диспечеризации запроса происходит поиск по словарю, соответствующего обработчика, с последующим его вызовом.
В обработке запроса ничего интересного нету.
Интерес предоставляет структура BatchJob. Используя ее в своем объекте, мы можем обеспечить периодичность выполнения некоторых действий (в нашем случае измерений).
type BatchJob struct {
Job func()
runJob bool
done chan bool
}
func (batchJob *BatchJob) Start() error {
if batchJob.runJob {
return errors.New("error: can't stop not stopped job")
}
if batchJob.Job == nil {
return errors.New("error: empty job function")
}
if !batchJob.runJob {
batchJob.done = make(chan bool, 1)
}
go batchJob.execution(batchJob.done)
batchJob.runJob = true
return nil
}
func (batchJob *BatchJob) IsRunning() bool {
return batchJob.runJob
}
func (batchJob *BatchJob) Stop() error {
if !batchJob.runJob {
return errors.New("error: can't stop not stopted job")
}
batchJob.runJob = false
isDone := <-batchJob.done
if isDone {
close(batchJob.done)
return nil
}
return errors.New("error: failed stop job")
}
func (batchJob *BatchJob) execution(done chan bool) {
for {
if batchJob.runJob {
batchJob.Job()
} else {
done <- true
return
}
}
}
Для этого в каждом обработчике запроса присутствует объект структуры BatchJob. Поле Job мы инстанцируем ссылкой на функцию измерения.
top.collectInfoJob.Job = top.collectInfo
top.lastRequestTime = time.Now()
err := top.collectInfoJob.Start()
Как я уже отмечал выше, если через 5 секунд сервис никто не опросит, то он уснет.
На мой взгляд, программа получилась очень простой и логичной. Каждый ее модуль выполняет конкретную функцию и может работать независимо друг от друга.
Все иcходные коды доступны на репозитарии GitHub github.com/Loafter/WebTop.
От себя хотелось бы еще раз сказать, что GO это удивительный и мощный язык. Надеюсь он займет доминирующую роль среди языков нового поколения.
Для тех, кто дочитал до конца, привожу ссылки на скомпилированные под разные архитектуры бинарники.
index.html — веб-интерфейс;
wtop-armv5-linux — версия для Linux (Android) arm v5;
wtop-armv6-linux — версия для Linux (Android) arm v6;
wtop-x64-linux — версия для Linux (Ubuntu… etc) X86-64.
Автор: Mopper