Pull to refresh

Простой диспетчер задач с веб-интерфейсом, на GO для Unix-систем, включая Android

Reading time 4 min
Views 31K
Простой диспетчер задач с веб-интерфейсом, написанный на языке 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.
Tags:
Hubs:
+33
Comments 5
Comments Comments 5

Articles