summaryrefslogtreecommitdiff
path: root/files/glt
diff options
context:
space:
mode:
Diffstat (limited to 'files/glt')
-rw-r--r--files/glt/stream-stats.go190
1 files changed, 190 insertions, 0 deletions
diff --git a/files/glt/stream-stats.go b/files/glt/stream-stats.go
new file mode 100644
index 00000000..5dec4e8f
--- /dev/null
+++ b/files/glt/stream-stats.go
@@ -0,0 +1,190 @@
+package main
+
+import (
+ "crypto/sha256"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "os"
+ "strconv"
+ "sync"
+ "time"
+)
+
+type LatestRequests map[string]bool
+
+var last5min LatestRequests
+var lMutex = &sync.Mutex{}
+
+const dateFormat = time.RFC3339
+
+func init() {
+ last5min = make(LatestRequests)
+}
+
+// find the next timestamp (i.e. time when minute is 4 mod 5 and second is 0)
+func nextTimestamp() time.Time {
+ now := time.Now()
+ if now.Minute()%5 == 4 && now.Second() == 0 {
+ return now.Add(5 * 60 * time.Second)
+ }
+
+ minDiff := 5
+ switch now.Minute() % 5 {
+ case 0:
+ minDiff = 4
+ case 1:
+ minDiff = 3
+ case 2:
+ minDiff = 2
+ case 3:
+ minDiff = 1
+ case 4:
+ minDiff = 5
+ }
+ return now.Add(-time.Duration(now.Second()) * time.Second).Add(time.Duration(minDiff) * 60 * time.Second)
+}
+
+// find the previous timestamp
+func previousTimestamp() time.Time {
+ now := time.Now()
+ if now.Minute()%5 == 4 && now.Second() == 0 {
+ return now.Add(-5 * 60 * time.Second)
+ }
+
+ minDiff := (now.Minute() % 5) + 1
+ return now.Add(-time.Duration(now.Second()) * time.Second).Add(-time.Duration(minDiff) * 60 * time.Second)
+}
+
+// writeToFile writes the 5min result to the file by appending data
+func writeToFile() {
+ filePath := os.Args[2]
+ timestamp := time.Now().Add(-5 * 60 * time.Second)
+ db := make(map[string]uint32)
+
+ // collect new count and erase data from 5-minutes data structure
+ lMutex.Lock()
+ latestCount := len(last5min)
+ last5min = make(LatestRequests)
+ lMutex.Unlock()
+
+ // read in existing data
+ content, err := ioutil.ReadFile(filePath)
+ if err == nil {
+ srcData := make(map[string]uint32)
+ err = json.Unmarshal(content, &srcData)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "failed to unmarshal file '%s': %s\n", filePath, err.Error())
+ return
+ }
+
+ // copy data over to database
+ for k, v := range srcData {
+ db[k] = v
+ }
+ }
+
+ // update database with latest count
+ db[timestamp.Format(dateFormat)] = uint32(latestCount)
+
+ // write to file
+ dump, _ := json.MarshalIndent(db, "", " ")
+ err = ioutil.WriteFile(filePath, dump, 0644)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "error while writing file '%s': %s\n", filePath, err.Error())
+ }
+}
+
+// handle a request to /
+func handle(w http.ResponseWriter, r *http.Request) {
+ w.Header().Add("Content-type", "text/plain; charset=utf-8")
+ _, err := w.Write([]byte("request counter\nby meisterluk\nroutes: {/req, /list}\n"))
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ }
+}
+
+// handle a request to /req
+// increments the counter within 5min plus one unless this client was already registered
+func handleRequest(w http.ResponseWriter, r *http.Request) {
+ // generate a key which detects trivial double requests
+ var ident string
+ ident = r.Header.Get("User-Agent")
+ if r.Header.Get("X-FORWARDED-FOR") != "" {
+ ident += r.Header.Get("X-FORWARDED-FOR")
+ }
+ if r.RemoteAddr != "" {
+ ident += r.RemoteAddr
+ }
+ //ident += time.Now().Format(dateFormat) // add this line to register every request for debugging
+ h := sha256.New()
+ key := string(h.Sum([]byte(ident)))
+
+ // register this client for counting
+ lMutex.Lock()
+ last5min[key] = true
+ defer lMutex.Unlock()
+
+ w.Write([]byte("request registered\n"))
+}
+
+func handleList(w http.ResponseWriter, r *http.Request) {
+ w.Header().Add("Content-type", "text/plain; charset=utf-8")
+ srcFile := os.Args[2]
+ db := make(map[string]uint32)
+
+ // add entry for current data
+ db[previousTimestamp().Format(dateFormat)] = uint32(len(last5min))
+
+ // read file
+ for {
+ content, err := ioutil.ReadFile(srcFile)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ break
+ }
+ srcDB := make(map[string]uint32)
+ err = json.Unmarshal(content, &srcDB)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ break
+ }
+
+ // copy data into db
+ for k, v := range srcDB {
+ db[k] = v
+ }
+ break // for loop used only for control flow
+ }
+
+ // print data
+ for timestamp, count := range db {
+ w.Write([]byte(timestamp + "\t" + strconv.Itoa(int(count)) + "\n"))
+ }
+}
+
+func main() {
+ if len(os.Args) != 3 {
+ fmt.Fprintln(os.Stderr, "usage: ./req-counter <int:port> <str:data-filepath>")
+ os.Exit(1)
+ }
+
+ http.HandleFunc("/", handle)
+ http.HandleFunc("/req", handleRequest)
+ http.HandleFunc("/list", handleList)
+
+ go func() {
+ for {
+ now := time.Now()
+ time.Sleep(nextTimestamp().Sub(now))
+
+ writeToFile()
+ fmt.Fprintf(os.Stderr, "File '%s' written.\n", os.Args[2])
+ }
+ }()
+
+ fmt.Println("listening on " + os.Args[1])
+ log.Fatal(http.ListenAndServe(os.Args[1], nil))
+}