- PVSM.RU - https://www.pvsm.ru -

Сбор статистики MTProto Proxy

Содержание

  • Предыстория
  • Сбор статистики
  • Отображение статистики
  • Визуализация и ведение статистики
  • Развертка
  • Заключение

Предыстория

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

Сбор статистики

На официальной странице MTProto Proxy на Docker Hub [1]указано что мы можем использовать команду docker exec mtproto-proxy curl http://localhost:2398/stats для получения статистики напрямую от MTProto Proxy который находится в контейнере, так что наш код будет выглядеть следующим образом.

package main

import (
    "io/ioutil"
    "net/http"
    "runtime"
    "strings"
    "time"
)

type User struct {
    Num string
}

var Users User

func CurrenUsers() (err error) {
    // Тянем статистику
    response, err := http.Get(`http://localhost:2398/stats`)
    if err != nil {
        return
    }
    body, err := ioutil.ReadAll(response.Body)
    if err != nil {
        return
    }
    defer response.Body.Close()
    stat := strings.Split(string(body), "n")
    for _, item := range stat {
        // Проверяем что у нас есть нужное поле
       // которое содержит количество пользователей
        if strings.HasPrefix(item, `total_special_connections`) {
            Users.Num = strings.Split(item, "t")[1]
        }
    }
    return nil
}

func main() {
    for {
        defer runtime.GC()
        CurrenUsers()
        time.Sleep(10 * time.Second)
    }
}

total_special_connections указано на том же Docker Hub [1]как число входящих подключений клиентов

Отображение статистики

Далее нам нужно в простой и удобной форме выводить текущее количество пользователей, мы будем выводить её в браузер.

package main

import (
    "html/template"
    "io/ioutil"
    "net/http"
    "runtime"
    "strings"
    "time"
)

type User struct {
    Num string
}

type HTML struct {
    IndexPage string
}

var Users User

var IndexTemplate = HTML{
    IndexPage: `<!DOCTYPE html>
    <html>
    
    <head>
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
            crossorigin="anonymous">
        <title>Stats</title>
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    </head>
    
    <body>
        <div class="container-fluid">
            <div class="row justify-content-center text-center" style="margin-top: 20%">
                <h1>Count of current users of MTProto Proxy: {{.Num}}</h1>
            </div>
        </div>
    </body>
    
    </html>`,
}

func CurrenUsers() (err error) {
    // Тянем статистику
    response, err := http.Get(`http://localhost:2398/stats`)
    if err != nil {
        return
    }
    body, err := ioutil.ReadAll(response.Body)
    if err != nil {
        return
    }
    defer response.Body.Close()
    stat := strings.Split(string(body), "n")
    for _, item := range stat {
        // Проверяем что у нас есть нужное поле
       // которое содержит количество пользователей
        if strings.HasPrefix(item, `total_special_connections`) {
            Users.Num = strings.Split(item, "t")[1]
        }
    }
    return nil
}

func sendStat(w http.ResponseWriter, r *http.Request) {
    if r.Method == "GET" {
        t := template.Must(template.New("indexpage").Parse(IndexTemplate.IndexPage))
        t.Execute(w, Users)
    }
}

func init() {
    go func() {
        for {
            defer runtime.GC()
            CurrenUsers()
            time.Sleep(10 * time.Second)
        }
    }()
}

func main() {
    http.HandleFunc("/", sendStat)
    http.ListenAndServe(":80", nil)
}

что такое init

init в любом случае будет вызван перед вызовом main

Теперь перейдя по IP адресу нашего MTProto Proxy мы сможем увидеть текущее количество клиентов.

image

Визуализация и ведение статистики

Есть много вариантов для визуализации и ведения статистики Datadog [2], Zabbix [3], Grafana [4], Graphite [5]. Я буду использовать Datadog. С помощью команды go get -u github.com/DataDog/datadog-go/statsd импортируем библиотеку statsd и используем её в коде.

package main

import (
    "github.com/DataDog/datadog-go/statsd"
    "html/template"
    "io/ioutil"
    "net/http"
    "os"
    "runtime"
    "strconv"
    "strings"
    "time"
)

var datadogIP = os.Getenv("DDGIP")
var tagName = os.Getenv("TGN")

type User struct {
    Num string
}

type HTML struct {
    IndexPage string
}

var Users User

var IndexTemplate = HTML{
    IndexPage: `<!DOCTYPE html>
    <html>
    
    <head>
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
            crossorigin="anonymous">
        <title>Stats</title>
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    </head>
    
    <body>
        <div class="container-fluid">
            <div class="row justify-content-center text-center" style="margin-top: 20%">
                <h1>Count of current users of MTProto Proxy: {{.Num}}</h1>
            </div>
        </div>
    </body>
    
    </html>`,
}

func (u User) convert() int64 {
    num, _ := strconv.Atoi(u.Num)
    return int64(num)
}

func CurrenUsers() (err error) {
    // Тянем статистику
    response, err := http.Get(`http://localhost:2398/stats`)
    if err != nil {
        return
    }
    body, err := ioutil.ReadAll(response.Body)
    if err != nil {
        return
    }
    defer response.Body.Close()
    stat := strings.Split(string(body), "n")
    for _, item := range stat {
        // Проверяем что у нас есть нужное поле
       // которое содержит количество пользователей
        if strings.HasPrefix(item, `total_special_connections`) {
            Users.Num = strings.Split(item, "t")[1]
        }
    }
    return nil
}

func sendStat(w http.ResponseWriter, r *http.Request) {
    if r.Method == "GET" {
        t := template.Must(template.New("indexpage").Parse(IndexTemplate.IndexPage))
        t.Execute(w, Users)
    }
}

func init() {
    go func() {
        for {
            defer runtime.GC()
            CurrenUsers()
            time.Sleep(10 * time.Second)
        }
    }()

    // Отправляем статистику агенту Datadog
    go func() {
        c, _ := statsd.New(datadogIP + ":8125")
        c.Namespace = "mtproto."
        c.Tags = append(c.Tags, tagName)
        for {
            c.Count("users.count", Users.convert(), nil, 1)
            time.Sleep(10 * time.Second)
        }
    }()
}

func main() {
    http.HandleFunc("/", sendStat)
    http.ListenAndServe(":80", nil)
}

Осталось собрать всё в докер образ

FROM telegrammessenger/proxy
COPY mtproto_proxy_stat .
RUN echo "$(tail -n +2 run.sh)" > run.sh && echo '#!/bin/bashn./mtproto_proxy_stat & disown' | cat - run.sh > temp && mv temp run.sh
CMD [ "/bin/sh", "-c", "/bin/bash /run.sh"] 

Развертка

Сначала нам нужно запустить контейнер с агентом Datadog

docker run -d --name dd-agent -v /var/run/docker.sock:/var/run/docker.sock:ro -v /proc/:/host/proc/:ro -v /sys/fs/cgroup/:/host/sys/fs/cgroup:ro -e DD_DOGSTATSD_NON_LOCAL_TRAFFIC=true -e DD_API_KEY=ВАШ_КЛЮЧ datadog/agent:latest

ВАЖНО для того чтобы мы могли слать агенту наши данные нужно установить значение true для переменной окружения DD_DOGSTATSD_NON_LOCAL_TRAFFIC

Далее с помощью команды docker inspect dd-agent нам нужно посмотреть IP контейнера чтобы слать ему данные

image

и запустить наш MTProto Proxy соединив его мостом с контейнером агента

docker run -d -p 443:443 -p 80:80 -e WORKERS=16 -e DDGIP=172.17.0.2 -e TGN=mtproto:main --link=dd-agent --name=mtproto --restart=always -v proxy-config:/data trigun117/mtproto_proxy_stat

И через пару минут мы уже можем построить график выбрав нужную метрику и источник (тег который указан при запуске контейнера с MTProto Proxy)

image

и отобразить на нём нашу статистику

image

Живой пример [6]

Заключение

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

Спасибо за уделенное внимание, предлагаю всем поделится мнением, замечаниями и предложениями в комментариях.

GitHub репозиторий [7]

Автор: trigun117

Источник [8]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/programmirovanie/284935

Ссылки в тексте:

[1] Docker Hub : https://hub.docker.com/r/telegrammessenger/proxy/

[2] Datadog: https://www.datadoghq.com/

[3] Zabbix: https://www.zabbix.com/

[4] Grafana: https://grafana.com/

[5] Graphite: https://graphiteapp.org/

[6] Живой пример: https://p.datadoghq.com/sb/3b4594398-19e893af4a4fad586e9f6722dc1c23ca

[7] GitHub репозиторий: https://github.com/trigun117/mtproto_proxy_stat

[8] Источник: https://habr.com/post/416087/?utm_campaign=416087