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

    Простой диспетчер задач с веб-интерфейсом, написанный на языке 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.
    • +33
    • 29.3k
    • 5
    Share post

    Similar posts

    Comments 5

      0
      Мы как пару лет используем нечто подобное на продакшне — github.com/iron-io/sshttp
        +3
        github.com/stealth/sshttp Вы об этом??
        Не уверен, утилита для того что бы совместить ssh/http на одном порту и диспечер задач с веб-интерфейсом чем то похожи
          0
          нет, я же вроде приложил ссылку (iron-io/sshttp)
          «This is a simple status daemon that's mean to run in the background and return some JSON of system information to an HTTP request.»
          у нас только не полноценный веб интерфейс а API с JSON
            +2
            Ваша ссылка, увы, показывает 404 ошибку

    Only users with full accounts can post comments. Log in, please.