Skip to content
Snippets Groups Projects
Commit a6c3fd87 authored by Lars Thoms's avatar Lars Thoms
Browse files

initial

parents
Branches
Tags 1.0.0
No related merge requests found
# HLS Subscriber Stats
To get meaningful statistics of a HTTP Live Stream (HLS), such as the current number of subscribers, it is necessary to analyze a *modified* `access.log`.
## Requirements
- golang
## Build
```shell
go build hls-subscriber-stats.go
```
## Usage (nginx)
1. Add an additional `log_format` to your `http` section:
```nginx
http {
log_format hls-subscriber-stats "$msec $request_filename";
}
```
2. Activate `access_log` in your `server` section:
```nginx
server {
access_log /path-log/hls-access.log hls-subscriber-stats;
}
```
3. Execute *Go* program in your `application` section:
```nginx
application stream {
live on;
hls on;
exec_push /path-bin/hls-subscriber-stats -input /path-log/hls-access.log -output "/path-www/$name.json" -name "$name" -interval 10 -segments 3
}
```
/*******************************************************************************
* parse access.log to receive the current number of HLS subscribers
*
* logformat: [UNIX TIMESTAMP (or msec)] [FILE]
* e.g.: 1639399770.294 /srv/domain.tld/hls/stream-name-12.ts
*
* @author Lars Thoms
* @date 2021-12-15
******************************************************************************/
package main
import (
"bufio"
"flag"
"log"
"os"
"regexp"
"strconv"
"time"
)
/*******************************************************************************
* distribution struct
******************************************************************************/
type distribution struct {
dist map[uint64]uint64
max uint64
}
func (d * distribution) Init() {
d.dist = make(map[uint64]uint64)
}
func (d *distribution) Count(i uint64) {
d.dist[i]++
if d.max < i {
d.max = i
}
}
func (d *distribution) Get(i uint64) uint64 {
return d.dist[i]
}
/*******************************************************************************
* functions
******************************************************************************/
/**
* @brief Parse access.log and generate a distribution
*
* @param path Path to access.log
* @param stream Name of the stream
* @param dist Distribution
*/
func ParseLog(path *string, stream *string, dist *distribution) {
// open access.log
file, err := os.Open(*path)
if err != nil {
log.Fatal(err)
}
defer file.Close()
// prepare filter
time := time.Now().Unix() / 100
time = 16393997
regex := regexp.MustCompile(
"^" + strconv.FormatInt(time, 10) + "[\\d]{2}\\.[\\d]+.*?" + regexp.QuoteMeta(*stream) + "-([\\d]+).ts$")
// iterate trough logfile
scanner := bufio.NewScanner(file)
for scanner.Scan() {
matches := regex.FindStringSubmatch(scanner.Text())
if matches != nil {
i, _ := strconv.ParseUint(matches[1], 10, 64)
dist.Count(i)
} else {
continue
}
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
}
/**
* @brief Generate stats.json with the current number of subscribers
*
* @param dist Distribution
* @param path Path to stats.json
* @param segments Number of stream segments to analyse
*/
func GenerateStat(dist *distribution, path *string, segments uint64) {
var subscribers uint64
for i := uint64(0); i < segments; i++ {
if s := dist.Get(dist.max - i); subscribers < s {
subscribers = s
}
}
// write stats as JSON to file
err := os.WriteFile(*path, []byte("{\"subscribers\":" + strconv.FormatUint(subscribers, 10) + "}"), 0644)
if err != nil {
log.Fatal(err)
}
}
/**
* @brief HLS Subscriber Stats
*/
func main() {
// CLI arguments
logfilePtr := flag.String("input", "access.log", "Path to access.log")
statsfilePtr := flag.String("output", "stream.json", "Path to stats.json")
streamPtr := flag.String("name", "stream", "Name of the stream")
segmentsPtr := flag.Uint64("segments", 3, "Number of stream segments to analyse")
intervalPtr := flag.Int("interval", 10, "Pause between parsing operations in seconds")
flag.Parse()
// parse log all n seconds
for {
distPtr := new(distribution)
distPtr.Init()
ParseLog(logfilePtr, streamPtr, distPtr)
GenerateStat(distPtr, statsfilePtr, *segmentsPtr)
time.Sleep(time.Duration(*intervalPtr) * time.Second)
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment