package main import ( "bufio" "context" "fmt" "io" "log" "net/http" "os" "regexp" "strconv" "github.com/docker/docker/api/types" "github.com/docker/docker/client" ) const COUNTER_SLOTS = 50 func main() { containerName, ok := os.LookupEnv("CONTAINER_NAME") if !ok { log.Fatalf("unable to read CONTAINER_NAME") } listen, ok := os.LookupEnv("LISTEN") if !ok { listen = ":8080" } defaultHeaders := map[string]string{"User-Agent": "engine-api-cli-1.0"} cli, err := client.NewClient("unix:///var/run/docker.sock", "v1.40", nil, defaultHeaders) if err != nil { log.Fatalf("unread: %s", err) } logs, err := cli.ContainerLogs(context.TODO(), containerName, types.ContainerLogsOptions{ ShowStdout: true, }) if err != nil { log.Fatalf("unread: %s", err) } defer logs.Close() counter := NewCounter() go counter.Scan(logs) http.HandleFunc("/", counter.MetricsHandler) err = http.ListenAndServe(listen, nil) log.Fatalf("unable to listen: %s", err) } type Counter struct { counters []int names []string current int } func NewCounter() *Counter { return &Counter{ counters: make([]int, COUNTER_SLOTS), names: make([]string, COUNTER_SLOTS), } } func (c *Counter) MetricsHandler(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "text/plain") for i, name := range c.names { fmt.Fprintf(w, "rc3_stream_count{name=%q} %d\n", name, c.counters[i]) } fmt.Fprintf(w, "rc3_stream_current{name=\"%d\"} 1\n", c.current) } func (c *Counter) ScanStdin() { c.Scan(os.Stdin) } func (c *Counter) Scan(in io.Reader) { matcher := regexp.MustCompile(`/hls/stream-([0-9]*).ts`) scanner := bufio.NewScanner(in) scanner.Split(bufio.ScanLines) for scanner.Scan() { matches := matcher.FindStringSubmatch(scanner.Text()) if len(matches) == 2 { c.countViewers(matches[1]) } } } func (c *Counter) countViewers(part string) { i, _ := strconv.Atoi(part) mod := i % COUNTER_SLOTS if c.names[mod] != part { c.counters[mod] = 0 c.current = i } c.counters[mod] += 1 c.names[mod] = part }