2016-01-17 10:31:39 -08:00
|
|
|
package main
|
|
|
|
|
2016-01-17 11:11:21 -08:00
|
|
|
import (
|
2016-04-10 17:19:30 -07:00
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
2016-01-17 11:11:21 -08:00
|
|
|
"flag"
|
2016-01-17 12:53:15 -08:00
|
|
|
"fmt"
|
2016-04-10 17:19:30 -07:00
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"os"
|
2016-01-17 12:53:15 -08:00
|
|
|
"strings"
|
2016-01-17 16:50:17 -08:00
|
|
|
"sync"
|
2016-04-10 17:19:30 -07:00
|
|
|
"time"
|
|
|
|
|
2017-11-13 15:57:20 -08:00
|
|
|
"github.com/FrankerFaceZ/FrankerFaceZ/socketserver/server"
|
2016-04-10 17:19:30 -07:00
|
|
|
"github.com/clarkduvall/hyperloglog"
|
2016-01-17 11:11:21 -08:00
|
|
|
)
|
2016-01-17 10:31:39 -08:00
|
|
|
|
2016-05-21 11:38:48 -07:00
|
|
|
var _ = os.Exit
|
|
|
|
|
2016-01-17 11:11:21 -08:00
|
|
|
var configLocation = flag.String("config", "./config.json", "Location of the configuration file. Defaults to ./config.json")
|
|
|
|
var genConfig = flag.Bool("genconf", false, "Generate a new configuration file.")
|
|
|
|
|
|
|
|
var config ConfigFile
|
|
|
|
|
|
|
|
const ExitCodeBadConfig = 2
|
|
|
|
|
2016-01-17 16:50:17 -08:00
|
|
|
var allServers []*serverInfo
|
|
|
|
|
2016-01-17 11:11:21 -08:00
|
|
|
func main() {
|
|
|
|
flag.Parse()
|
|
|
|
|
|
|
|
if *genConfig {
|
|
|
|
makeConfig()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
loadConfig()
|
|
|
|
|
2016-01-17 16:50:17 -08:00
|
|
|
allServers = make([]*serverInfo, len(ServerNames))
|
|
|
|
for i, v := range ServerNames {
|
|
|
|
allServers[i] = &serverInfo{}
|
|
|
|
allServers[i].Setup(v)
|
|
|
|
}
|
|
|
|
|
2016-05-21 11:38:48 -07:00
|
|
|
//printEveryDay()
|
|
|
|
//os.Exit(0)
|
2016-01-17 14:09:09 -08:00
|
|
|
http.HandleFunc("/api/get", ServeAPIGet)
|
2016-01-17 11:11:21 -08:00
|
|
|
http.ListenAndServe(config.ListenAddr, http.DefaultServeMux)
|
|
|
|
}
|
|
|
|
|
2016-04-10 17:19:30 -07:00
|
|
|
func printEveryDay() {
|
|
|
|
year := 2015
|
|
|
|
month := 12
|
|
|
|
day := 23
|
|
|
|
filter := serverFilterAll
|
|
|
|
var filter1, filter2, filter3 serverFilter
|
|
|
|
filter1.Mode = serverFilterModeWhitelist
|
|
|
|
filter2.Mode = serverFilterModeWhitelist
|
|
|
|
filter3.Mode = serverFilterModeWhitelist
|
|
|
|
filter1.Add(allServers[0].subdomain)
|
|
|
|
filter2.Add(allServers[1].subdomain)
|
|
|
|
filter3.Add(allServers[2].subdomain)
|
|
|
|
stopTime := time.Now()
|
|
|
|
var at time.Time
|
|
|
|
const timeFmt = "2006-01-02"
|
|
|
|
for ; stopTime.After(at); day++ {
|
|
|
|
at = time.Date(year, time.Month(month), day, 0, 0, 0, 0, server.CounterLocation)
|
|
|
|
hll, _ := hyperloglog.NewPlus(server.CounterPrecision)
|
|
|
|
hll1, _ := hyperloglog.NewPlus(server.CounterPrecision)
|
|
|
|
hll2, _ := hyperloglog.NewPlus(server.CounterPrecision)
|
|
|
|
hll3, _ := hyperloglog.NewPlus(server.CounterPrecision)
|
|
|
|
addSingleDate(at, filter, hll)
|
|
|
|
addSingleDate(at, filter1, hll1)
|
|
|
|
addSingleDate(at, filter2, hll2)
|
|
|
|
addSingleDate(at, filter3, hll3)
|
|
|
|
fmt.Printf("%s\t%d\t%d\t%d\t%d\n", at.Format(timeFmt), hll.Count(), hll1.Count(), hll2.Count(), hll3.Count())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-17 12:53:15 -08:00
|
|
|
const RequestURIName = "q"
|
|
|
|
const separatorRange = "~"
|
|
|
|
const separatorAdd = " "
|
2016-01-17 14:09:09 -08:00
|
|
|
const separatorServer = "@"
|
2016-01-17 12:53:15 -08:00
|
|
|
const jsonErrMalformedRequest = `{"status":"error","error":"malformed request uri"}`
|
|
|
|
const jsonErrBlankRequest = `{"status":"error","error":"no queries given"}`
|
|
|
|
const statusError = "error"
|
|
|
|
const statusPartial = "partial"
|
|
|
|
const statusOk = "ok"
|
2016-01-17 16:50:17 -08:00
|
|
|
|
2016-01-17 12:53:15 -08:00
|
|
|
type apiResponse struct {
|
2016-04-10 17:19:30 -07:00
|
|
|
Status string `json:"status"`
|
2016-01-17 12:53:15 -08:00
|
|
|
Responses []requestResponse `json:"resp"`
|
|
|
|
}
|
2016-01-17 16:50:17 -08:00
|
|
|
|
2016-01-17 12:53:15 -08:00
|
|
|
type requestResponse struct {
|
2016-04-10 17:19:30 -07:00
|
|
|
Status string `json:"status"`
|
2016-01-17 12:53:15 -08:00
|
|
|
Request string `json:"req"`
|
2016-04-10 17:19:30 -07:00
|
|
|
Error string `json:"error,omitempty"`
|
|
|
|
Count uint64 `json:"count,omitempty"`
|
2016-01-17 12:53:15 -08:00
|
|
|
}
|
|
|
|
|
2016-01-17 14:09:09 -08:00
|
|
|
func ServeAPIGet(w http.ResponseWriter, r *http.Request) {
|
2016-01-17 12:53:15 -08:00
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
|
|
|
|
u, err := url.ParseRequestURI(r.RequestURI)
|
|
|
|
if err != nil {
|
|
|
|
w.WriteHeader(400)
|
|
|
|
fmt.Fprint(w, jsonErrMalformedRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
query := u.Query()
|
|
|
|
reqCount := len(query[RequestURIName])
|
|
|
|
if reqCount == 0 {
|
|
|
|
w.WriteHeader(400)
|
|
|
|
fmt.Fprint(w, jsonErrBlankRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
resp := apiResponse{Status: statusOk}
|
|
|
|
resp.Responses = make([]requestResponse, reqCount)
|
|
|
|
for i, v := range query[RequestURIName] {
|
2016-01-17 14:09:09 -08:00
|
|
|
if len(v) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
resp.Responses[i] = ProcessSingleGetRequest(v)
|
2016-01-17 12:53:15 -08:00
|
|
|
}
|
|
|
|
for _, v := range resp.Responses {
|
2016-01-17 16:50:17 -08:00
|
|
|
if v.Status != statusOk {
|
2016-01-17 12:53:15 -08:00
|
|
|
resp.Status = statusPartial
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
w.WriteHeader(200)
|
|
|
|
enc := json.NewEncoder(w)
|
|
|
|
enc.Encode(resp)
|
|
|
|
}
|
|
|
|
|
2016-01-17 16:50:17 -08:00
|
|
|
var errRangeFormatIncorrect = errors.New("incorrect range format, must be yyyy-mm-dd~yyyy-mm-dd")
|
2016-01-17 14:09:09 -08:00
|
|
|
|
|
|
|
// ProcessSingleGetRequest takes a request string and pulls the unique user data for the given dates and filters.
|
|
|
|
//
|
|
|
|
// The request string is in the following format:
|
|
|
|
//
|
|
|
|
// Request = AddDateRanges [ "@" ServerFilter ] .
|
|
|
|
// ServerFilter = [ "!" ] ServerName { " " ServerName } .
|
|
|
|
// ServerName = { "a" … "z" } .
|
|
|
|
// AddDateRanges = DateMaybeRange { " " DateMaybeRange } .
|
|
|
|
// DateMaybeRange = DateRange | Date .
|
|
|
|
// DateRange = Date "~" Date .
|
|
|
|
// Date = Year "-" Month "-" Day .
|
|
|
|
// Year = number number number number .
|
|
|
|
// Month = number number .
|
|
|
|
// Day = number number .
|
|
|
|
// number = "0" … "9" .
|
|
|
|
//
|
|
|
|
// Example of a well-formed request:
|
|
|
|
//
|
|
|
|
// 2016-01-04~2016-01-08 2016-01-11~2016-01-15@andknuckles tuturu
|
|
|
|
//
|
|
|
|
// Remember that spaces are urlencoded as "+", so the HTTP request to send to retrieve that data would be this:
|
|
|
|
//
|
|
|
|
// /api/get?q=2016-01-04~2016-01-08+2016-01-11~2016-01-15%40andknuckles+tuturu
|
|
|
|
//
|
|
|
|
// If a ServerFilter is specified, only users connecting to the specified servers will be included in the count.
|
|
|
|
//
|
|
|
|
// It does not matter if a date is specified multiple times, due to the data format used.
|
|
|
|
func ProcessSingleGetRequest(req string) (result requestResponse) {
|
2016-01-17 16:50:17 -08:00
|
|
|
fmt.Println("processing request:", req)
|
|
|
|
hll, _ := hyperloglog.NewPlus(server.CounterPrecision)
|
2016-01-17 12:53:15 -08:00
|
|
|
|
|
|
|
result.Request = req
|
|
|
|
result.Status = statusOk
|
2016-01-17 14:09:09 -08:00
|
|
|
filter := serverFilterAll
|
|
|
|
|
|
|
|
collectError := func(err error) bool {
|
2016-01-17 16:50:17 -08:00
|
|
|
if err == ErrServerInFailedState {
|
|
|
|
result.Status = statusPartial
|
|
|
|
return false
|
|
|
|
} else if err != nil {
|
2016-01-17 14:09:09 -08:00
|
|
|
result.Status = statusError
|
|
|
|
result.Error = err.Error()
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
serverSplit := strings.Split(req, separatorServer)
|
|
|
|
if len(serverSplit) == 2 {
|
|
|
|
filter = serverFilterNone
|
|
|
|
serversOnly := strings.Split(serverSplit[1], separatorAdd)
|
|
|
|
for _, v := range serversOnly {
|
|
|
|
filter.Add(v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
addSplit := strings.Split(serverSplit[0], separatorAdd)
|
2016-01-17 12:53:15 -08:00
|
|
|
|
2016-04-10 17:19:30 -07:00
|
|
|
outerLoop:
|
2016-01-17 12:53:15 -08:00
|
|
|
for _, split1 := range addSplit {
|
|
|
|
if len(split1) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
rangeSplit := strings.Split(split1, separatorRange)
|
|
|
|
if len(rangeSplit) == 1 {
|
2016-01-17 14:09:09 -08:00
|
|
|
at, err := parseDateFromRequest(rangeSplit[0])
|
|
|
|
if collectError(err) {
|
2016-01-17 12:53:15 -08:00
|
|
|
break outerLoop
|
|
|
|
}
|
2016-01-17 14:09:09 -08:00
|
|
|
|
2016-01-17 16:50:17 -08:00
|
|
|
err = addSingleDate(at, filter, hll)
|
2016-01-17 14:09:09 -08:00
|
|
|
if collectError(err) {
|
2016-01-17 12:53:15 -08:00
|
|
|
break outerLoop
|
|
|
|
}
|
|
|
|
} else if len(rangeSplit) == 2 {
|
2016-01-17 14:09:09 -08:00
|
|
|
from, err := parseDateFromRequest(rangeSplit[0])
|
|
|
|
if collectError(err) {
|
2016-01-17 12:53:15 -08:00
|
|
|
break outerLoop
|
|
|
|
}
|
2016-01-17 14:09:09 -08:00
|
|
|
to, err := parseDateFromRequest(rangeSplit[1])
|
|
|
|
if collectError(err) {
|
2016-01-17 12:53:15 -08:00
|
|
|
break outerLoop
|
|
|
|
}
|
2016-01-17 14:09:09 -08:00
|
|
|
|
2016-01-17 16:50:17 -08:00
|
|
|
err = addRange(from, to, filter, hll)
|
2016-01-17 14:09:09 -08:00
|
|
|
if collectError(err) {
|
2016-01-17 12:53:15 -08:00
|
|
|
break outerLoop
|
|
|
|
}
|
|
|
|
} else {
|
2016-01-17 14:09:09 -08:00
|
|
|
collectError(errRangeFormatIncorrect)
|
2016-01-17 12:53:15 -08:00
|
|
|
break outerLoop
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if result.Status == statusOk {
|
|
|
|
result.Count = hll.Count()
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
var errBadDate = errors.New("bad date format, must be yyyy-mm-dd")
|
|
|
|
var zeroTime = time.Unix(0, 0)
|
|
|
|
|
2016-01-17 14:09:09 -08:00
|
|
|
func parseDateFromRequest(dateStr string) (time.Time, error) {
|
2016-01-17 12:53:15 -08:00
|
|
|
var year, month, day int
|
|
|
|
n, err := fmt.Sscanf(dateStr, "%d-%d-%d", &year, &month, &day)
|
|
|
|
if err != nil || n != 3 {
|
|
|
|
return zeroTime, errBadDate
|
|
|
|
}
|
2016-01-17 16:50:17 -08:00
|
|
|
return time.Date(year, time.Month(month), day, 0, 0, 0, 0, server.CounterLocation), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type hllAndError struct {
|
|
|
|
hll *hyperloglog.HyperLogLogPlus
|
|
|
|
err error
|
2016-01-17 12:53:15 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
func addSingleDate(at time.Time, filter serverFilter, dest *hyperloglog.HyperLogLogPlus) error {
|
2016-01-17 16:50:17 -08:00
|
|
|
var partialErr error
|
|
|
|
for _, si := range allServers {
|
|
|
|
if filter.IsServerAllowed(si) {
|
|
|
|
hll, err2 := si.GetHLL(at)
|
|
|
|
if err2 == ErrServerInFailedState {
|
|
|
|
partialErr = err2
|
|
|
|
} else if err2 != nil {
|
|
|
|
return err2
|
|
|
|
} else {
|
|
|
|
dest.Merge(hll)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return partialErr
|
2016-01-17 12:53:15 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
func addRange(start time.Time, end time.Time, filter serverFilter, dest *hyperloglog.HyperLogLogPlus) error {
|
2016-01-17 16:50:17 -08:00
|
|
|
end = server.TruncateToMidnight(end)
|
|
|
|
year, month, day := start.Date()
|
|
|
|
var partialErr error
|
|
|
|
var myAllServers = make([]*serverInfo, 0, len(allServers))
|
|
|
|
for _, si := range allServers {
|
|
|
|
if filter.IsServerAllowed(si) {
|
|
|
|
myAllServers = append(myAllServers, si)
|
|
|
|
}
|
|
|
|
}
|
2016-01-17 12:53:15 -08:00
|
|
|
|
2016-01-17 16:50:17 -08:00
|
|
|
var ch = make(chan hllAndError)
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
for current := start; current.Before(end); day = day + 1 {
|
2016-01-17 11:11:21 -08:00
|
|
|
current = time.Date(year, month, day, 0, 0, 0, 0, server.CounterLocation)
|
2016-01-17 16:50:17 -08:00
|
|
|
for _, si := range myAllServers {
|
|
|
|
wg.Add(1)
|
|
|
|
go getHLL(ch, si, current)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
wg.Wait()
|
|
|
|
close(ch)
|
|
|
|
}()
|
2016-01-17 11:11:21 -08:00
|
|
|
|
2016-01-17 16:50:17 -08:00
|
|
|
for pair := range ch {
|
|
|
|
wg.Done()
|
|
|
|
hll, err := pair.hll, pair.err
|
|
|
|
if err != nil {
|
|
|
|
if partialErr == nil || partialErr == ErrServerInFailedState {
|
|
|
|
partialErr = err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
dest.Merge(hll)
|
|
|
|
}
|
2016-01-17 11:11:21 -08:00
|
|
|
}
|
2016-01-17 16:50:17 -08:00
|
|
|
|
|
|
|
return partialErr
|
|
|
|
}
|
|
|
|
|
|
|
|
func getHLL(ch chan hllAndError, si *serverInfo, at time.Time) {
|
|
|
|
hll, err := si.GetHLL(at)
|
|
|
|
ch <- hllAndError{hll: hll, err: err}
|
2016-04-10 17:19:30 -07:00
|
|
|
}
|