From e92adea56f1aadea8a6e43871530c9b3a78e27c7 Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Sun, 23 Apr 2017 01:03:40 +0200 Subject: drop no needed mysql support --- src/hub/Makefile | 2 -- src/hub/src/spreadspace.org/sfive-hub/s5hub.go | 3 +-- src/hub/src/spreadspace.org/sfive/s5srv.go | 4 ++-- src/hub/src/spreadspace.org/sfive/s5store.go | 24 +++++++---------------- src/hub/src/spreadspace.org/sfive/s5store_test.go | 10 +++++----- 5 files changed, 15 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/hub/Makefile b/src/hub/Makefile index 0c34f14..d03dc7d 100644 --- a/src/hub/Makefile +++ b/src/hub/Makefile @@ -43,8 +43,6 @@ LIBS := "gopkg.in/gorp.v2" \ "github.com/zenazn/goji" \ "github.com/pborman/uuid" \ "github.com/equinox0815/graphite-golang" -# "github.com/go-sql-driver/mysql" -# "github.com/ziutek/mymysql/godrv" all: build test diff --git a/src/hub/src/spreadspace.org/sfive-hub/s5hub.go b/src/hub/src/spreadspace.org/sfive-hub/s5hub.go index 8897e42..ccdd4e0 100644 --- a/src/hub/src/spreadspace.org/sfive-hub/s5hub.go +++ b/src/hub/src/spreadspace.org/sfive-hub/s5hub.go @@ -13,7 +13,6 @@ var s5hl = log.New(os.Stderr, "[s5hub]\t", log.LstdFlags) func main() { db := flag.String("db", "/var/lib/sfive/db.sqlite", "path to the sqlite3 database file") - dbMysql := flag.Bool("db-mysql", false, "use MySQL connector") pipe := flag.String("pipe", "/var/run/sfive/pipe", "path to the unix pipe for the pipeserver") ppipe := flag.String("pipegram", "/var/run/sfive/pipegram", "path to the unix datagram pipe for the pipeserver") startPipe := flag.Bool("start-pipe-server", true, "start a connection oriented pipe server; see option pipe") @@ -38,7 +37,7 @@ func main() { return } - server, err := sfive.NewServer(*dbMysql, *db) + server, err := sfive.NewServer(*db) if err != nil { s5hl.Fatalf("failed to initialize: %v", err) } diff --git a/src/hub/src/spreadspace.org/sfive/s5srv.go b/src/hub/src/spreadspace.org/sfive/s5srv.go index 9cf4c64..8f9093d 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srv.go +++ b/src/hub/src/spreadspace.org/sfive/s5srv.go @@ -168,10 +168,10 @@ func (self StatsSinkServer) Close() { self.store.Close() } -func NewServer(mysql bool, dbPath string) (server *StatsSinkServer, err error) { +func NewServer(dbPath string) (server *StatsSinkServer, err error) { // TODO read configuration and create instance with correct settings server = new(StatsSinkServer) - server.store, err = NewStore(mysql, dbPath) + server.store, err = NewStore(dbPath) if err != nil { return } diff --git a/src/hub/src/spreadspace.org/sfive/s5store.go b/src/hub/src/spreadspace.org/sfive/s5store.go index 6fb5f8a..8611661 100644 --- a/src/hub/src/spreadspace.org/sfive/s5store.go +++ b/src/hub/src/spreadspace.org/sfive/s5store.go @@ -66,26 +66,16 @@ func updateFromStatisticsData(value StatisticsData) (dataUpdateDb, []clientDataD return du, cd, src, tags } -func initDb(mysql bool, path string) (res *gorp.DbMap, hubId string, err error) { +func initDb(path string) (res *gorp.DbMap, hubId string, err error) { // connect to db using standard Go database/sql API var db *sql.DB - var dialect gorp.Dialect - if mysql { - db, err = sql.Open("mysql", path) - if err != nil { - return - } - dialect = gorp.MySQLDialect{Engine: "InnoDB", Encoding: "UTF8"} - } else { - db, err = sql.Open("sqlite3", path) - if err != nil { - return - } - dialect = gorp.SqliteDialect{} + db, err = sql.Open("sqlite3", path) + if err != nil { + return } - dbmap := &gorp.DbMap{Db: db, Dialect: dialect} + dbmap := &gorp.DbMap{Db: db, Dialect: gorp.SqliteDialect{}} // dbmap.TraceOn("[gorp]", log.New(os.Stdout, "myapp:", log.Lmicroseconds)) dbmap.AddTableWithName(tagDb{}, tagsTn).SetKeys(true, "Id").ColMap("Name").SetUnique(true) @@ -630,8 +620,8 @@ func (s sqliteStore) GetStoreId() (uuid string, err error) { return } -func NewStore(mysql bool, path string) (sqliteStore, error) { - db, hubid, err := initDb(mysql, path) +func NewStore(path string) (sqliteStore, error) { + db, hubid, err := initDb(path) if err != nil { return sqliteStore{}, err } diff --git a/src/hub/src/spreadspace.org/sfive/s5store_test.go b/src/hub/src/spreadspace.org/sfive/s5store_test.go index 409bd08..6c9e302 100644 --- a/src/hub/src/spreadspace.org/sfive/s5store_test.go +++ b/src/hub/src/spreadspace.org/sfive/s5store_test.go @@ -6,7 +6,7 @@ import ( ) func TestAppend(t *testing.T) { - store, err := NewStore(false, "file:memdb1?mode=memory&cache=shared") + store, err := NewStore("file:memdb1?mode=memory&cache=shared") if err != nil { t.Errorf("Failed to initialize: %v", err) return @@ -53,7 +53,7 @@ func TestAppend(t *testing.T) { } func TestCount(t *testing.T) { - store, err := NewStore(false, "file:memdb1?mode=memory&cache=shared") + store, err := NewStore("file:memdb1?mode=memory&cache=shared") if err != nil { t.Errorf("Failed to initialize: %v", err) } @@ -67,7 +67,7 @@ func TestCount(t *testing.T) { } func TestGetUpdatesAfter(t *testing.T) { - store, err := NewStore(false, "file:memdb1?mode=memory&cache=shared") + store, err := NewStore("file:memdb1?mode=memory&cache=shared") if err != nil { t.Errorf("Failed to initialize: %v", err) return @@ -142,7 +142,7 @@ func generateStatisticsData(n int) (data []StatisticsData) { } func BenchmarkAppendMany(b *testing.B) { - store, err := NewStore(false, "file:memdb1?mode=memory&cache=shared") + store, err := NewStore("file:memdb1?mode=memory&cache=shared") if err != nil { b.Errorf("Failed to initialize: %v", err) } @@ -157,7 +157,7 @@ func BenchmarkAppendMany(b *testing.B) { } func BenchmarkGetUpdatesAfter(b *testing.B) { - store, err := NewStore(false, "file:memdb1?mode=memory&cache=shared") + store, err := NewStore("file:memdb1?mode=memory&cache=shared") if err != nil { b.Errorf("Failed to initialize: %v", err) } -- cgit v1.2.3 From ee795c82891f2554bfbed4f485b8883292910d6a Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Sun, 23 Apr 2017 02:36:44 +0200 Subject: moved client data to bolt db --- src/hub/Makefile | 1 + src/hub/src/spreadspace.org/sfive-hub/s5hub.go | 2 +- src/hub/src/spreadspace.org/sfive/s5srv.go | 10 +- src/hub/src/spreadspace.org/sfive/s5store.go | 107 +++++++++++++--------- src/hub/src/spreadspace.org/sfive/s5store_test.go | 32 ++++++- src/hub/src/spreadspace.org/sfive/s5typesStore.go | 35 ++----- 6 files changed, 108 insertions(+), 79 deletions(-) (limited to 'src') diff --git a/src/hub/Makefile b/src/hub/Makefile index d03dc7d..43b4d9d 100644 --- a/src/hub/Makefile +++ b/src/hub/Makefile @@ -40,6 +40,7 @@ EXECUTEABLE := sfive-hub LIBS := "gopkg.in/gorp.v2" \ "github.com/mattn/go-sqlite3" \ + "github.com/boltdb/bolt" \ "github.com/zenazn/goji" \ "github.com/pborman/uuid" \ "github.com/equinox0815/graphite-golang" diff --git a/src/hub/src/spreadspace.org/sfive-hub/s5hub.go b/src/hub/src/spreadspace.org/sfive-hub/s5hub.go index ccdd4e0..7ff58b5 100644 --- a/src/hub/src/spreadspace.org/sfive-hub/s5hub.go +++ b/src/hub/src/spreadspace.org/sfive-hub/s5hub.go @@ -12,7 +12,7 @@ import ( var s5hl = log.New(os.Stderr, "[s5hub]\t", log.LstdFlags) func main() { - db := flag.String("db", "/var/lib/sfive/db.sqlite", "path to the sqlite3 database file") + db := flag.String("db", "/var/lib/sfive/", "directory to store the database files") pipe := flag.String("pipe", "/var/run/sfive/pipe", "path to the unix pipe for the pipeserver") ppipe := flag.String("pipegram", "/var/run/sfive/pipegram", "path to the unix datagram pipe for the pipeserver") startPipe := flag.Bool("start-pipe-server", true, "start a connection oriented pipe server; see option pipe") diff --git a/src/hub/src/spreadspace.org/sfive/s5srv.go b/src/hub/src/spreadspace.org/sfive/s5srv.go index 8f9093d..3dbd6e7 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srv.go +++ b/src/hub/src/spreadspace.org/sfive/s5srv.go @@ -1,6 +1,9 @@ package sfive -import "time" +import ( + "path/filepath" + "time" +) type appendManyToken struct { data []StatisticsData @@ -170,8 +173,11 @@ func (self StatsSinkServer) Close() { func NewServer(dbPath string) (server *StatsSinkServer, err error) { // TODO read configuration and create instance with correct settings + sqlitePath := filepath.Join(dbPath, "db.sqlite") + boltPath := filepath.Join(dbPath, "db.bolt") + server = new(StatsSinkServer) - server.store, err = NewStore(dbPath) + server.store, err = NewStore(sqlitePath, boltPath) if err != nil { return } diff --git a/src/hub/src/spreadspace.org/sfive/s5store.go b/src/hub/src/spreadspace.org/sfive/s5store.go index 8611661..a3bd8ce 100644 --- a/src/hub/src/spreadspace.org/sfive/s5store.go +++ b/src/hub/src/spreadspace.org/sfive/s5store.go @@ -2,17 +2,21 @@ package sfive import ( "database/sql" + "encoding/binary" + "encoding/json" "fmt" "time" + "github.com/boltdb/bolt" _ "github.com/mattn/go-sqlite3" "github.com/pborman/uuid" "gopkg.in/gorp.v2" ) type sqliteStore struct { - db *gorp.DbMap - hubId string + db *gorp.DbMap + dbBolt *bolt.DB + hubId string } func tagsFromStatisticsData(value StatisticsData) []tagDb { @@ -36,14 +40,6 @@ func sourceFromStatisticsData(value StatisticsData) sourceDb { } } -func clientsFromStatisticsData(value StatisticsData) []clientDataDb { - res := make([]clientDataDb, len(value.Data.Clients)) - for i := range value.Data.Clients { - res[i] = clientDataDb{-1, -1, value.Data.Clients[i]} - } - return res -} - func dataUpdateFromStatisticsData(value StatisticsData) dataUpdateDb { return dataUpdateDb{ -1, @@ -57,37 +53,48 @@ func dataUpdateFromStatisticsData(value StatisticsData) dataUpdateDb { value.Data.BytesSent} } -func updateFromStatisticsData(value StatisticsData) (dataUpdateDb, []clientDataDb, sourceDb, []tagDb) { +func updateFromStatisticsData(value StatisticsData) (dataUpdateDb, []ClientData, sourceDb, []tagDb) { du := dataUpdateFromStatisticsData(value) - cd := clientsFromStatisticsData(value) + cd := value.Data.Clients src := sourceFromStatisticsData(value) tags := tagsFromStatisticsData(value) return du, cd, src, tags } -func initDb(path string) (res *gorp.DbMap, hubId string, err error) { - // connect to db using standard Go database/sql API - var db *sql.DB +func initDbBolt(boltPath string) (boltDb *bolt.DB, err error) { + boltDb, err = bolt.Open(boltPath, 0600, &bolt.Options{Timeout: 1 * time.Second}) + if err != nil { + return + } - db, err = sql.Open("sqlite3", path) + err = boltDb.Update(func(tx *bolt.Tx) error { + _, err := tx.CreateBucketIfNotExists([]byte(clientDataBn)) + return err + }) + + return +} + +func initDbSqlite(sqlitePath string) (dbmap *gorp.DbMap, hubId string, err error) { + + var db *sql.DB + db, err = sql.Open("sqlite3", sqlitePath) if err != nil { return } - dbmap := &gorp.DbMap{Db: db, Dialect: gorp.SqliteDialect{}} + dbmap = &gorp.DbMap{Db: db, Dialect: gorp.SqliteDialect{}} // dbmap.TraceOn("[gorp]", log.New(os.Stdout, "myapp:", log.Lmicroseconds)) dbmap.AddTableWithName(tagDb{}, tagsTn).SetKeys(true, "Id").ColMap("Name").SetUnique(true) dbmap.AddTableWithName(sourceTagsDb{}, sourceTagsTn).SetKeys(false, "TagId", "SourceId") dbmap.AddTableWithName(sourceDb{}, sourcesTn).SetKeys(true, "Id").SetUniqueTogether("ContentId", "Format", "Quality", "Hostname") - dbmap.AddTableWithName(clientDataDb{}, clientdataUpdatesTn).SetKeys(true, "Id") dbmap.AddTableWithName(dataUpdateDb{}, dataUpdatesTn).SetKeys(true, "Id") dbmap.AddTableWithName(hubInfoDb{}, hubInfoTn).SetKeys(false, "Name") // TODO use some real migration, yadda yadda - err = dbmap.CreateTablesIfNotExists() - if err != nil { + if err = dbmap.CreateTablesIfNotExists(); err != nil { return } @@ -102,7 +109,6 @@ func initDb(path string) (res *gorp.DbMap, hubId string, err error) { } } - res = dbmap return } @@ -274,19 +280,25 @@ func (s sqliteStore) insertDataUpdateEntry(src sourceDb, du *dataUpdateDb) (err return } -func (s sqliteStore) insertDataUpdateClientEntries(cd []clientDataDb, du dataUpdateDb) (err error) { - for i := range cd { - cd[i].DataUpdatesId = du.Id - err = s.db.Insert(&cd[i]) +// itob returns an 8-byte big endian representation of v. +func itob(v int) []byte { + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, uint64(v)) + return b +} + +func (s sqliteStore) insertDataUpdateClientEntries(cd []ClientData, du dataUpdateDb) error { + return s.dbBolt.Update(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(clientDataBn)) + jsonData, err := json.Marshal(cd) if err != nil { - // TODO - return + return err } - } - return + return b.Put(itob(du.Id), jsonData) + }) } -func (s sqliteStore) appendItem(du dataUpdateDb, cd []clientDataDb, src sourceDb, tags []tagDb) (err error) { +func (s sqliteStore) appendItem(du dataUpdateDb, cd []ClientData, src sourceDb, tags []tagDb) (err error) { err = s.insertNewTags(tags) if err != nil { return @@ -407,15 +419,13 @@ func (s sqliteStore) GetUpdate(id int) (res dataUpdateDb, err error) { return } -func (s sqliteStore) GetClientsByUpdateId(id int) (res []clientDataDb, err error) { - var qres []interface{} - qres, err = s.db.Select(clientDataDb{}, "select * from "+clientdataUpdatesTn+" where DataUpdatesId = ?", id) - if err == nil { - res = make([]clientDataDb, len(qres)) - for i := range qres { - res[i] = *qres[i].(*clientDataDb) - } - } +func (s sqliteStore) GetClientsByUpdateId(id int) (res []ClientData, err error) { + err = s.dbBolt.Update(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(clientDataBn)) + + jsonData := b.Get(itob(id)) + return json.Unmarshal(jsonData, &res) + }) return } @@ -437,8 +447,8 @@ var ( ) func (s sqliteStore) CreateStatisticsDataFrom(dat dataUpdateQueryResult) (res StatisticsData, err error) { - var clientsDb []clientDataDb - clientsDb, err = s.GetClientsByUpdateId(dat.Id) + var clients []ClientData + clients, err = s.GetClientsByUpdateId(dat.Id) if err != nil { s5l.Printf("store GetClients failed: %v", err) return @@ -453,7 +463,7 @@ func (s sqliteStore) CreateStatisticsDataFrom(dat dataUpdateQueryResult) (res St res.StreamId.ContentId = dat.ContentId res.StreamId.Format = dat.Format res.StreamId.Quality = dat.Quality - res.CopyFromClientDataDb(clientsDb) + res.Data.Clients = clients res.Tags = tagsDb return } @@ -620,14 +630,21 @@ func (s sqliteStore) GetStoreId() (uuid string, err error) { return } -func NewStore(path string) (sqliteStore, error) { - db, hubid, err := initDb(path) +func NewStore(sqlitePath, boltPath string) (sqliteStore, error) { + boltDb, err := initDbBolt(boltPath) + if err != nil { + return sqliteStore{}, err + } + + db, hubid, err := initDbSqlite(sqlitePath) if err != nil { + boltDb.Close() return sqliteStore{}, err } - return sqliteStore{db, hubid}, nil + return sqliteStore{db, boltDb, hubid}, nil } func (s sqliteStore) Close() { s.db.Db.Close() + s.dbBolt.Close() } diff --git a/src/hub/src/spreadspace.org/sfive/s5store_test.go b/src/hub/src/spreadspace.org/sfive/s5store_test.go index 6c9e302..74721c4 100644 --- a/src/hub/src/spreadspace.org/sfive/s5store_test.go +++ b/src/hub/src/spreadspace.org/sfive/s5store_test.go @@ -1,12 +1,30 @@ package sfive import ( + "fmt" + "os" + "os/user" "testing" "time" ) +var ( + __sqlitePath = "file:memdb1?mode=memory&cache=shared" + __boltPath = "/run/s5hub_testing_db.bolt" +) + +func TestMain(m *testing.M) { + u, err := user.Current() + if err != nil { + os.Exit(-1) + } + __boltPath = fmt.Sprintf("/run/user/%s/s5hub_testing_db.bolt", u.Uid) + os.Exit(m.Run()) +} + func TestAppend(t *testing.T) { - store, err := NewStore("file:memdb1?mode=memory&cache=shared") + os.Remove(__boltPath) + store, err := NewStore(__sqlitePath, __boltPath) if err != nil { t.Errorf("Failed to initialize: %v", err) return @@ -53,7 +71,8 @@ func TestAppend(t *testing.T) { } func TestCount(t *testing.T) { - store, err := NewStore("file:memdb1?mode=memory&cache=shared") + os.Remove(__boltPath) + store, err := NewStore(__sqlitePath, __boltPath) if err != nil { t.Errorf("Failed to initialize: %v", err) } @@ -67,7 +86,8 @@ func TestCount(t *testing.T) { } func TestGetUpdatesAfter(t *testing.T) { - store, err := NewStore("file:memdb1?mode=memory&cache=shared") + os.Remove(__boltPath) + store, err := NewStore(__sqlitePath, __boltPath) if err != nil { t.Errorf("Failed to initialize: %v", err) return @@ -142,7 +162,8 @@ func generateStatisticsData(n int) (data []StatisticsData) { } func BenchmarkAppendMany(b *testing.B) { - store, err := NewStore("file:memdb1?mode=memory&cache=shared") + os.Remove(__boltPath) + store, err := NewStore(__sqlitePath, __boltPath) if err != nil { b.Errorf("Failed to initialize: %v", err) } @@ -157,7 +178,8 @@ func BenchmarkAppendMany(b *testing.B) { } func BenchmarkGetUpdatesAfter(b *testing.B) { - store, err := NewStore("file:memdb1?mode=memory&cache=shared") + os.Remove(__boltPath) + store, err := NewStore(__sqlitePath, __boltPath) if err != nil { b.Errorf("Failed to initialize: %v", err) } diff --git a/src/hub/src/spreadspace.org/sfive/s5typesStore.go b/src/hub/src/spreadspace.org/sfive/s5typesStore.go index f06e953..19c9404 100644 --- a/src/hub/src/spreadspace.org/sfive/s5typesStore.go +++ b/src/hub/src/spreadspace.org/sfive/s5typesStore.go @@ -9,12 +9,13 @@ import ( // table names const ( - tagsTn = "Tags" - sourceTagsTn = "StreamToTagMap" - sourcesTn = "Sources" - clientdataUpdatesTn = "ClientDataUpdates" - dataUpdatesTn = "DataUpdates" - hubInfoTn = "HubInfo" + tagsTn = "Tags" + sourceTagsTn = "StreamToTagMap" + sourcesTn = "Sources" + dataUpdatesTn = "DataUpdates" + hubInfoTn = "HubInfo" + + clientDataBn = "ClientData" ) type hubInfoDb struct { @@ -42,14 +43,6 @@ type sourceDb struct { SourceId } -// stored in clientdataUpdatesTn -// ClientData n:1 DataUpdate -type clientDataDb struct { - Id int - DataUpdatesId int // foreign key to dataUpdatesTn - ClientData -} - // stored in dataUpdatesTn // in DB, StatisticsData/DataUpdate is flattened compared to JSON DTOs type dataUpdateDb struct { @@ -99,22 +92,12 @@ func (self *StatisticsData) CopyFromDataUpdateDb(value dataUpdateDb, hubId strin self.Data.BytesSent = value.BytesSent } -func (self *StatisticsData) CopyFromClientDataDb(values []clientDataDb) { - clients := make([]ClientData, len(values)) - for i := range values { - clients[i].Ip = values[i].Ip - clients[i].UserAgent = values[i].UserAgent - clients[i].BytesSent = values[i].BytesSent - } - self.Data.Clients = clients -} - func cvtToApiStatisticsData( - hubId string, source sourceDb, update dataUpdateDb, clients []clientDataDb, tags []tagDb) StatisticsData { + hubId string, source sourceDb, update dataUpdateDb, clients []ClientData, tags []tagDb) StatisticsData { res := StatisticsData{} res.CopyFromSourceDb(source) res.CopyFromDataUpdateDb(update, hubId) - res.CopyFromClientDataDb(clients) + res.Data.Clients = clients res.CopyFromTagsDb(tags) return res } -- cgit v1.2.3 From db2d54d210edc06a445fde0facb30d1f37576cf1 Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Sun, 23 Apr 2017 16:10:11 +0200 Subject: revamp test scripts, simpified tag insertion --- dat/.gitignore | 1 + src/hub/.gitignore | 1 + src/hub/dump-test-db | 3 -- src/hub/src/spreadspace.org/sfive/s5store.go | 57 +++++++---------------- src/hub/src/spreadspace.org/sfive/s5store_test.go | 2 +- src/hub/test-bolt | 14 ++++++ src/hub/test-bolter | 14 ++++++ src/hub/test-client | 8 ++-- src/hub/test-collector | 4 -- src/hub/test-fwd | 5 ++ src/hub/test-fwd-es | 4 +- src/hub/test-fwd-piwik | 5 +- src/hub/test-import | 6 ++- src/hub/test-sqlite | 5 ++ src/hub/test-srv | 7 ++- 15 files changed, 77 insertions(+), 59 deletions(-) create mode 100644 dat/.gitignore delete mode 100755 src/hub/dump-test-db create mode 100755 src/hub/test-bolt create mode 100755 src/hub/test-bolter delete mode 100755 src/hub/test-collector create mode 100755 src/hub/test-fwd create mode 100755 src/hub/test-sqlite (limited to 'src') diff --git a/dat/.gitignore b/dat/.gitignore new file mode 100644 index 0000000..39fa2b7 --- /dev/null +++ b/dat/.gitignore @@ -0,0 +1 @@ +/sample-accesslog.json diff --git a/src/hub/.gitignore b/src/hub/.gitignore index 16dc168..7be1b96 100644 --- a/src/hub/.gitignore +++ b/src/hub/.gitignore @@ -5,3 +5,4 @@ /pkg *.a *.o +/test diff --git a/src/hub/dump-test-db b/src/hub/dump-test-db deleted file mode 100755 index 9a7aae7..0000000 --- a/src/hub/dump-test-db +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -echo .dump | sqlite3 /var/lib/sfive/db.sqlite - diff --git a/src/hub/src/spreadspace.org/sfive/s5store.go b/src/hub/src/spreadspace.org/sfive/s5store.go index a3bd8ce..6d6a9ad 100644 --- a/src/hub/src/spreadspace.org/sfive/s5store.go +++ b/src/hub/src/spreadspace.org/sfive/s5store.go @@ -7,6 +7,10 @@ import ( "fmt" "time" + // needed for gorp tracing + // "log" + // "os" + "github.com/boltdb/bolt" _ "github.com/mattn/go-sqlite3" "github.com/pborman/uuid" @@ -204,15 +208,11 @@ func (s sqliteStore) findTag(name string) (tag *tagDb, err error) { func (s sqliteStore) insertNewTags(tags []tagDb) (err error) { for i := range tags { var t *tagDb - t, err = s.findTag(tags[i].Name) - if err != nil { - _, err = s.db.Exec("insert into "+tagsTn+" VALUES (NULL, ?)", tags[i].Name) - } - t, err = s.findTag(tags[i].Name) - - if err == nil { + if t, err = s.findTag(tags[i].Name); err == nil { tags[i] = *t - } else { + continue + } + if err = s.db.Insert(&(tags[i])); err != nil { break } } @@ -239,14 +239,11 @@ func (s sqliteStore) findSource(src sourceDb) (res *sourceDb, err error) { func (s sqliteStore) insertNewSource(src *sourceDb) (err error) { var t *sourceDb - t, err = s.findSource(*src) - if err == nil { + if t, err = s.findSource(*src); err == nil { *src = *t - } else { - err = s.db.Insert(src) + return } - - return + return s.db.Insert(src) } func (s sqliteStore) insertSourceTagLinks(src sourceDb, tags []tagDb) (err error) { @@ -299,52 +296,32 @@ func (s sqliteStore) insertDataUpdateClientEntries(cd []ClientData, du dataUpdat } func (s sqliteStore) appendItem(du dataUpdateDb, cd []ClientData, src sourceDb, tags []tagDb) (err error) { - err = s.insertNewTags(tags) - if err != nil { + if err = s.insertNewTags(tags); err != nil { return } - err = s.insertNewSource(&src) - if err != nil { + if err = s.insertNewSource(&src); err != nil { //fmt.Printf("src\n") return } - err = s.insertSourceTagLinks(src, tags) - if err != nil { + if err = s.insertSourceTagLinks(src, tags); err != nil { return } - err = s.insertDataUpdateEntry(src, &du) - if err != nil { + if err = s.insertDataUpdateEntry(src, &du); err != nil { return } - err = s.insertDataUpdateClientEntries(cd, du) - if err != nil { + if err = s.insertDataUpdateClientEntries(cd, du); err != nil { return } return } -// this function is the biggest pile of copy/pasted crap while sick that is still compilable. func (s sqliteStore) Append(update StatisticsData) (err error) { - var tx *gorp.Transaction - tx, err = s.db.Begin() - if err != nil { - return - } - - du, cd, src, tags := updateFromStatisticsData(update) - //s5l.Printf("blah: %v", du) - err = s.appendItem(du, cd, src, tags) - if err != nil { - tx.Rollback() - return - } - - return tx.Commit() + return s.AppendMany([]StatisticsData{update}) } func (s sqliteStore) AppendMany(updates []StatisticsData) (err error) { diff --git a/src/hub/src/spreadspace.org/sfive/s5store_test.go b/src/hub/src/spreadspace.org/sfive/s5store_test.go index 74721c4..92ad149 100644 --- a/src/hub/src/spreadspace.org/sfive/s5store_test.go +++ b/src/hub/src/spreadspace.org/sfive/s5store_test.go @@ -102,7 +102,7 @@ func TestGetUpdatesAfter(t *testing.T) { err = store.Append(dat) if err != nil { - t.Errorf("Failed to retrieve: %v", err) + t.Errorf("Failed to append: %v", err) return } diff --git a/src/hub/test-bolt b/src/hub/test-bolt new file mode 100755 index 0000000..7ac3dd2 --- /dev/null +++ b/src/hub/test-bolt @@ -0,0 +1,14 @@ +#!/bin/sh + +TEST_D="./test" + +BIN="$(go env GOPATH)/bin/bolt" +if [ ! -x "$BIN" ]; then + echo "bolt not found. Please run:" + echo "" + echo " go get -u github.com/boltdb/bolt/..." + echo "" + exit 1 +fi + +exec "$(go env GOPATH)/bin/bolt" $@ "$TEST_D/db.bolt" diff --git a/src/hub/test-bolter b/src/hub/test-bolter new file mode 100755 index 0000000..dc2e98d --- /dev/null +++ b/src/hub/test-bolter @@ -0,0 +1,14 @@ +#!/bin/sh + +TEST_D="./test" + +BIN="$(go env GOPATH)/bin/bolter" +if [ ! -x "$BIN" ]; then + echo "bolter not found. Please run:" + echo "" + echo " go get -u github.com/hasit/bolter" + echo "" + exit 1 +fi + +exec $BIN --file "$TEST_D/db.bolt" $@ diff --git a/src/hub/test-client b/src/hub/test-client index 8a24c7d..d5756e7 100755 --- a/src/hub/test-client +++ b/src/hub/test-client @@ -1,11 +1,14 @@ #!/bin/sh + +TEST_D="./test" + echo pipe: import sample.json echo ------------------------ -socat file:../../dat/sample.json,rdonly unix-client:/run/sfive/pipe +socat file:../../dat/sample.json,rdonly "unix-client:$TEST_D/pipe" echo pipe-gram: import sample-gram.json echo ---------------------------------- -while read x; do echo "$x" | socat stdio unix-sendto:/run/sfive/pipegram; done < ../../dat/sample-gram.json +while read x; do echo "$x" | socat stdio "unix-sendto:$TEST_D/pipegram"; done < ../../dat/sample-gram.json echo show query result echo ----------------- @@ -20,4 +23,3 @@ echo ---------- curl -i 'http://localhost:8000/stats' echo '\n\ndone' - diff --git a/src/hub/test-collector b/src/hub/test-collector deleted file mode 100755 index e6ca367..0000000 --- a/src/hub/test-collector +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -./bin/sfive-hub -db "file:memdb1?mode=memory&cache=shared" -start-pipe-server=false -start-pipegram-server=false -start-web-server -viz-dir "$(pwd)/../viz" -bind=":8000" - diff --git a/src/hub/test-fwd b/src/hub/test-fwd new file mode 100755 index 0000000..eff7605 --- /dev/null +++ b/src/hub/test-fwd @@ -0,0 +1,5 @@ +#!/bin/sh + +TEST_D="./test" + +exec ./bin/sfive-hub -db "$TEST_D" -start-pipe-server=false -start-pipegram-server=false -start-web-server=false -forward-url="http://localhost:8000" diff --git a/src/hub/test-fwd-es b/src/hub/test-fwd-es index 3f3eb12..dc7979f 100755 --- a/src/hub/test-fwd-es +++ b/src/hub/test-fwd-es @@ -1,5 +1,5 @@ #!/bin/sh -rm -f /run/sfive/pipe /run/sfive/pipegram -./bin/sfive-hub -start-pipe-server=false -start-pipegram-server=false -start-web-server=false -db db.sqlite -forward-es-url="http://stream.elevate.at:9200/e14" +TEST_D="./test" +exec ./bin/sfive-hub -db "$TEST_D" -start-pipe-server=false -start-pipegram-server=false -start-web-server=false -forward-es-url="http://stream.elevate.at:9200/e14" diff --git a/src/hub/test-fwd-piwik b/src/hub/test-fwd-piwik index 1d45219..b6ac640 100755 --- a/src/hub/test-fwd-piwik +++ b/src/hub/test-fwd-piwik @@ -1,4 +1,5 @@ #!/bin/sh -rm -f /run/sfive/pipe /run/sfive/pipegram -./bin/sfive-hub -start-pipe-server=false -start-pipegram-server=false -start-web-server=false -db db.sqlite -forward-piwik-url="http://localhost/piwik.php" -piwik-token "asdfjlkasjdflk" -piwik-site-id 4 -piwik-site-url "https://stream.elevate.at" +TEST_D="./test" + +exec ./bin/sfive-hub -db "$TEST_D" -start-pipe-server=false -start-pipegram-server=false -start-web-server=false -forward-piwik-url="http://localhost/piwik.php" -piwik-token "asdfjlkasjdflk" -piwik-site-id 4 -piwik-site-url "https://stream.elevate.at" diff --git a/src/hub/test-import b/src/hub/test-import index d1a5044..a6c2942 100755 --- a/src/hub/test-import +++ b/src/hub/test-import @@ -1,10 +1,12 @@ #!/bin/sh + +TEST_D="./test" + echo pipe: import sample.json echo ------------------------ [ -f ../../dat/sample-access.json ] || zcat ../../dat/sample-accesslog.json.gz > ../../dat/sample-accesslog.json -socat file:../../dat/sample-accesslog.json,rdonly unix-client:/run/sfive/pipe +socat file:../../dat/sample-accesslog.json,rdonly "unix-client:$TEST_D/pipe" echo '\n\ndone' - diff --git a/src/hub/test-sqlite b/src/hub/test-sqlite new file mode 100755 index 0000000..1c2dcb1 --- /dev/null +++ b/src/hub/test-sqlite @@ -0,0 +1,5 @@ +#!/bin/sh + +TEST_D="./test" + +exec sqlite3 "$TEST_D/db.sqlite" diff --git a/src/hub/test-srv b/src/hub/test-srv index 254549a..37d5897 100755 --- a/src/hub/test-srv +++ b/src/hub/test-srv @@ -1,4 +1,7 @@ #!/bin/sh -rm -f /run/sfive/pipe /run/sfive/pipegram -./bin/sfive-hub -db /var/lib/sfive/db.sqlite -start-pipe-server -pipe /var/run/sfive/pipe -start-pipegram-server -pipegram /var/run/sfive/pipegram -start-web-server -viz-dir "$(pwd)/../viz" -bind=":8001" +TEST_D="./test" + +mkdir -p "$TEST_D" +rm -f "$TEST_D/pipe" "$TEST_D/pipegram" +exec ./bin/sfive-hub -db "$TEST_D" -start-pipe-server -pipe "$TEST_D/pipe" -start-pipegram-server -pipegram "$TEST_D/pipegram" -start-web-server -viz-dir "$(pwd)/../viz" -bind=":8000" -- cgit v1.2.3 From 41dd3f9f0ce32bdd0c31ea0fba6d547dbe7f3453 Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Sun, 23 Apr 2017 16:39:24 +0200 Subject: minor web interface fix --- src/hub/src/spreadspace.org/sfive/s5srvWeb.go | 7 ++++--- src/hub/src/spreadspace.org/sfive/s5store.go | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/hub/src/spreadspace.org/sfive/s5srvWeb.go b/src/hub/src/spreadspace.org/sfive/s5srvWeb.go index af9e986..eac610e 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvWeb.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvWeb.go @@ -13,8 +13,9 @@ import ( "github.com/zenazn/goji/web" ) -func hello(c web.C, w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "Hello, %s!", c.URLParams["name"]) +func (self StatsSinkServer) healthz(c web.C, w http.ResponseWriter, r *http.Request) { + // TODO: do a more sophisticated check + fmt.Fprintf(w, "OK\n") } func (self StatsSinkServer) getTagList(c web.C, w http.ResponseWriter, r *http.Request) { @@ -243,7 +244,7 @@ func (self StatsSinkServer) ServeWeb(vizAppLocation string) { } } - goji.Get("/hello/:name", hello) + goji.Get("/healthz", self.healthz) goji.Get("/tags", self.getTagList) goji.Get("/sources", self.getSourcesList) goji.Get("/sources/:id", self.getSource) diff --git a/src/hub/src/spreadspace.org/sfive/s5store.go b/src/hub/src/spreadspace.org/sfive/s5store.go index 6d6a9ad..cd95076 100644 --- a/src/hub/src/spreadspace.org/sfive/s5store.go +++ b/src/hub/src/spreadspace.org/sfive/s5store.go @@ -346,13 +346,13 @@ func (s sqliteStore) AppendMany(updates []StatisticsData) (err error) { func castArrayToString(value []interface{}) []string { res := make([]string, len(value)) for i := range value { - res[i] = value[i].(string) + res[i] = value[i].(*tagDb).Name } return res } func (s sqliteStore) GetTags() ([]string, error) { - res, dbErr := s.db.Select("", "select Name from "+tagsTn) + res, dbErr := s.db.Select(tagDb{}, "select Name from "+tagsTn) if dbErr == nil { sRes := castArrayToString(res) return sRes, nil -- cgit v1.2.3 From 58d925ecbc3b2f246e8c1e77e05b03962aed824b Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Sun, 23 Apr 2017 17:28:28 +0200 Subject: no warning on expected error on client disconnect --- src/hub/src/spreadspace.org/sfive/s5srvPipe.go | 9 +++++++-- src/hub/src/spreadspace.org/sfive/s5store.go | 6 ++++++ 2 files changed, 13 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/hub/src/spreadspace.org/sfive/s5srvPipe.go b/src/hub/src/spreadspace.org/sfive/s5srvPipe.go index 8084461..efc190c 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvPipe.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvPipe.go @@ -2,6 +2,7 @@ package sfive import ( "bufio" + "io" "net" ) @@ -9,7 +10,9 @@ func (self StatsSinkServer) handleConnection(conn net.Conn) { reader := bufio.NewReader(conn) buffer, err := reader.ReadBytes('\n') if err != nil { - s5l.Printf("pipe: failed to read from connection: %v\n", err) + if err != io.EOF { + s5l.Printf("pipe: failed to read from connection: %v\n", err) + } return } marshaller, err := NewStatefulDecoder(buffer) @@ -21,7 +24,9 @@ func (self StatsSinkServer) handleConnection(conn net.Conn) { for { buffer, err := reader.ReadBytes('\n') if err != nil { - s5l.Printf("pipe: failed to read from connection: %v\n", err) + if err != io.EOF { + s5l.Printf("pipe: failed to read from connection: %v\n", err) + } return } diff --git a/src/hub/src/spreadspace.org/sfive/s5store.go b/src/hub/src/spreadspace.org/sfive/s5store.go index cd95076..bbfce00 100644 --- a/src/hub/src/spreadspace.org/sfive/s5store.go +++ b/src/hub/src/spreadspace.org/sfive/s5store.go @@ -285,6 +285,9 @@ func itob(v int) []byte { } func (s sqliteStore) insertDataUpdateClientEntries(cd []ClientData, du dataUpdateDb) error { + if len(cd) == 0 { + return nil + } return s.dbBolt.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(clientDataBn)) jsonData, err := json.Marshal(cd) @@ -401,6 +404,9 @@ func (s sqliteStore) GetClientsByUpdateId(id int) (res []ClientData, err error) b := tx.Bucket([]byte(clientDataBn)) jsonData := b.Get(itob(id)) + if jsonData == nil { + return nil + } return json.Unmarshal(jsonData, &res) }) return -- cgit v1.2.3 From fcf90066c7175e6d7a64575170b46595bff6da32 Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Sun, 23 Apr 2017 19:13:07 +0200 Subject: set fill precentage for client data bucket to 100% --- src/hub/src/spreadspace.org/sfive/s5store.go | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/hub/src/spreadspace.org/sfive/s5store.go b/src/hub/src/spreadspace.org/sfive/s5store.go index bbfce00..64424dc 100644 --- a/src/hub/src/spreadspace.org/sfive/s5store.go +++ b/src/hub/src/spreadspace.org/sfive/s5store.go @@ -290,6 +290,7 @@ func (s sqliteStore) insertDataUpdateClientEntries(cd []ClientData, du dataUpdat } return s.dbBolt.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(clientDataBn)) + b.FillPercent = 1.0 // we only do append jsonData, err := json.Marshal(cd) if err != nil { return err -- cgit v1.2.3 From ebd66dc169799e6d29783da9448ff429c6a30c4f Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Mon, 24 Apr 2017 00:22:03 +0200 Subject: store user agents in a seperate bucket --- src/hub/src/spreadspace.org/sfive/s5store.go | 78 ++++++++++++++++++++--- src/hub/src/spreadspace.org/sfive/s5typesApi.go | 6 +- src/hub/src/spreadspace.org/sfive/s5typesStore.go | 14 +++- 3 files changed, 85 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/hub/src/spreadspace.org/sfive/s5store.go b/src/hub/src/spreadspace.org/sfive/s5store.go index 64424dc..e8f82b5 100644 --- a/src/hub/src/spreadspace.org/sfive/s5store.go +++ b/src/hub/src/spreadspace.org/sfive/s5store.go @@ -73,7 +73,13 @@ func initDbBolt(boltPath string) (boltDb *bolt.DB, err error) { } err = boltDb.Update(func(tx *bolt.Tx) error { - _, err := tx.CreateBucketIfNotExists([]byte(clientDataBn)) + if _, err := tx.CreateBucketIfNotExists([]byte(clientDataBn)); err != nil { + return err + } + if _, err := tx.CreateBucketIfNotExists([]byte(userAgentsFwdBn)); err != nil { + return err + } + _, err := tx.CreateBucketIfNotExists([]byte(userAgentsRevBn)) return err }) @@ -277,21 +283,58 @@ func (s sqliteStore) insertDataUpdateEntry(src sourceDb, du *dataUpdateDb) (err return } -// itob returns an 8-byte big endian representation of v. func itob(v int) []byte { b := make([]byte, 8) binary.BigEndian.PutUint64(b, uint64(v)) return b } +func btoi(b []byte) int { + return int(binary.BigEndian.Uint64(b)) +} + +func (s sqliteStore) insertNewUserAgent(tx *bolt.Tx, ua string) (uaId int, err error) { + bf := tx.Bucket([]byte(userAgentsFwdBn)) + bf.FillPercent = 1.0 // we only do appends + br := tx.Bucket([]byte(userAgentsRevBn)) + br.FillPercent = 1.0 // we only do appends + + bUaId := bf.Get([]byte(ua)) + if bUaId != nil { + return btoi(bUaId), nil + } + + next, _ := bf.NextSequence() + uaId = int(next) + if err = bf.Put([]byte(ua), itob(uaId)); err != nil { + return + } + if err = br.Put(itob(uaId), []byte(ua)); err != nil { + return + } + + return uaId, err +} + func (s sqliteStore) insertDataUpdateClientEntries(cd []ClientData, du dataUpdateDb) error { if len(cd) == 0 { return nil } + return s.dbBolt.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(clientDataBn)) - b.FillPercent = 1.0 // we only do append - jsonData, err := json.Marshal(cd) + b.FillPercent = 1.0 // we only do appends + + data := []clientDataDb{} + for _, c := range cd { + uaId, err := s.insertNewUserAgent(tx, c.UserAgent) + if err != nil { + return err + } + data = append(data, clientDataDb{c.Ip, uaId, c.BytesSent}) + } + + jsonData, err := json.Marshal(data) if err != nil { return err } @@ -401,14 +444,27 @@ func (s sqliteStore) GetUpdate(id int) (res dataUpdateDb, err error) { } func (s sqliteStore) GetClientsByUpdateId(id int) (res []ClientData, err error) { - err = s.dbBolt.Update(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte(clientDataBn)) + err = s.dbBolt.View(func(tx *bolt.Tx) error { + bc := tx.Bucket([]byte(clientDataBn)) + bu := tx.Bucket([]byte(userAgentsRevBn)) - jsonData := b.Get(itob(id)) + jsonData := bc.Get(itob(id)) if jsonData == nil { return nil } - return json.Unmarshal(jsonData, &res) + data := []clientDataDb{} + if err := json.Unmarshal(jsonData, &data); err != nil { + return err + } + for _, c := range data { + cd := ClientData{Ip: c.Ip, BytesSent: c.BytesSent} + ua := bu.Get(itob(c.UserAgentId)) + if ua != nil { + cd.UserAgent = string(ua) + } + res = append(res, cd) + } + return nil }) return } @@ -475,6 +531,12 @@ func (s sqliteStore) GetUpdatesAfter(id int) (res []StatisticsData, err error) { } func (s sqliteStore) GetUpdates(filter *StatsFilter) (res []StatisticsData, err error) { + limit := 5000 + if filter.limit == nil { + filter.limit = &limit + } else if *filter.limit > limit { + *filter.limit = limit + } sourceSql, parameters := getFilteredDataUpdateSelect(filter) sql := "SELECT " + updateColumnSelect + " FROM " + sourceSql s5tl.Printf("store: sql: %s", sql) diff --git a/src/hub/src/spreadspace.org/sfive/s5typesApi.go b/src/hub/src/spreadspace.org/sfive/s5typesApi.go index 5b2b29f..515b869 100644 --- a/src/hub/src/spreadspace.org/sfive/s5typesApi.go +++ b/src/hub/src/spreadspace.org/sfive/s5typesApi.go @@ -23,15 +23,15 @@ type SourceId struct { type ClientData struct { Ip string `json:"ip"` - UserAgent string `json:"user-agent"` + UserAgent string `json:"user-agent,omitempty"` BytesSent uint `json:"bytes-sent"` } type SourceData struct { ClientCount uint `json:"client-count"` - BytesReceived uint `json:"bytes-received"` + BytesReceived uint `json:"bytes-received,omitempty"` BytesSent uint `json:"bytes-sent"` - Clients []ClientData `json:"clients"` + Clients []ClientData `json:"clients,omitempty"` } type DataUpdate struct { diff --git a/src/hub/src/spreadspace.org/sfive/s5typesStore.go b/src/hub/src/spreadspace.org/sfive/s5typesStore.go index 19c9404..a177aea 100644 --- a/src/hub/src/spreadspace.org/sfive/s5typesStore.go +++ b/src/hub/src/spreadspace.org/sfive/s5typesStore.go @@ -7,15 +7,18 @@ import ( // compared to JSON DTOs, DB types are flattened, and use key-relations instead of collections // this is very much not normalized at all, because I'm too lazy to type -// table names const ( + // sqlite table names tagsTn = "Tags" sourceTagsTn = "StreamToTagMap" sourcesTn = "Sources" dataUpdatesTn = "DataUpdates" hubInfoTn = "HubInfo" - clientDataBn = "ClientData" + // bolt bucket names + clientDataBn = "ClientData" + userAgentsFwdBn = "UserAgentsFwd" + userAgentsRevBn = "UserAgentsRev" ) type hubInfoDb struct { @@ -43,6 +46,13 @@ type sourceDb struct { SourceId } +// stored in clientDataBn +type clientDataDb struct { + Ip string `json:"ip"` + UserAgentId int `json:"ua"` + BytesSent uint `json:"bs"` +} + // stored in dataUpdatesTn // in DB, StatisticsData/DataUpdate is flattened compared to JSON DTOs type dataUpdateDb struct { -- cgit v1.2.3 From 9a92b9d61b5a9d1822d030c710a84aa0a789d6d2 Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Thu, 27 Apr 2017 00:40:01 +0200 Subject: also move source info into bolt --- src/hub/src/spreadspace.org/sfive/s5srvWeb.go | 22 +- src/hub/src/spreadspace.org/sfive/s5store.go | 543 ++++++++-------------- src/hub/src/spreadspace.org/sfive/s5store_test.go | 50 +- src/hub/src/spreadspace.org/sfive/s5typesStore.go | 102 ++-- 4 files changed, 274 insertions(+), 443 deletions(-) (limited to 'src') diff --git a/src/hub/src/spreadspace.org/sfive/s5srvWeb.go b/src/hub/src/spreadspace.org/sfive/s5srvWeb.go index eac610e..aa98532 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvWeb.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvWeb.go @@ -18,21 +18,6 @@ func (self StatsSinkServer) healthz(c web.C, w http.ResponseWriter, r *http.Requ fmt.Fprintf(w, "OK\n") } -func (self StatsSinkServer) getTagList(c web.C, w http.ResponseWriter, r *http.Request) { - const resourceName = "tags" - values, err := self.store.GetTags() - if err != nil { - http.Error(w, fmt.Sprintf("failed to retrieve %s: %v", resourceName, err), http.StatusInternalServerError) - return - } - jsonString, err := json.Marshal(DataContainer{values}) - if err != nil { - http.Error(w, fmt.Sprintf("failed to marshal %s: %v", resourceName, err), http.StatusInternalServerError) - return - } - fmt.Fprintf(w, "%s", jsonString) -} - func (self StatsSinkServer) getSourcesList(c web.C, w http.ResponseWriter, r *http.Request) { const resourceName = "sources" values, err := self.store.GetSources() @@ -57,7 +42,11 @@ func (self StatsSinkServer) getSource(c web.C, w http.ResponseWriter, r *http.Re } value, err := self.store.GetSource(int(id)) if err != nil { - http.Error(w, fmt.Sprintf("failed to retrieve %s: %v", resourceName, err), http.StatusInternalServerError) + if err == ErrNotFound { + http.Error(w, fmt.Sprintf("failed to retrieve %s: %v", resourceName, err), http.StatusNotFound) + } else { + http.Error(w, fmt.Sprintf("failed to retrieve %s: %v", resourceName, err), http.StatusInternalServerError) + } return } jsonString, err := json.Marshal(value) @@ -245,7 +234,6 @@ func (self StatsSinkServer) ServeWeb(vizAppLocation string) { } goji.Get("/healthz", self.healthz) - goji.Get("/tags", self.getTagList) goji.Get("/sources", self.getSourcesList) goji.Get("/sources/:id", self.getSource) goji.Get("/updates", self.getUpdateList) diff --git a/src/hub/src/spreadspace.org/sfive/s5store.go b/src/hub/src/spreadspace.org/sfive/s5store.go index e8f82b5..07a73e9 100644 --- a/src/hub/src/spreadspace.org/sfive/s5store.go +++ b/src/hub/src/spreadspace.org/sfive/s5store.go @@ -2,7 +2,6 @@ package sfive import ( "database/sql" - "encoding/binary" "encoding/json" "fmt" "time" @@ -23,24 +22,15 @@ type sqliteStore struct { hubId string } -func tagsFromStatisticsData(value StatisticsData) []tagDb { - tags := make([]tagDb, len(value.SourceId.Tags)) - for i := range value.SourceId.Tags { - tags[i] = tagDb{Id: -1, Name: value.SourceId.Tags[i]} - } - return tags -} - func sourceFromStatisticsData(value StatisticsData) sourceDb { return sourceDb{ - -1, - StreamId{ + Hostname: value.SourceId.Hostname, + StreamId: streamIdDb{ ContentId: value.SourceId.StreamId.ContentId, Format: value.SourceId.StreamId.Format, Quality: value.SourceId.StreamId.Quality, }, - SourceId{ - Hostname: value.SourceId.Hostname}, + Tags: value.SourceId.Tags, } } @@ -57,13 +47,12 @@ func dataUpdateFromStatisticsData(value StatisticsData) dataUpdateDb { value.Data.BytesSent} } -func updateFromStatisticsData(value StatisticsData) (dataUpdateDb, []ClientData, sourceDb, []tagDb) { +func updateFromStatisticsData(value StatisticsData) (dataUpdateDb, []ClientData, sourceDb) { du := dataUpdateFromStatisticsData(value) cd := value.Data.Clients src := sourceFromStatisticsData(value) - tags := tagsFromStatisticsData(value) - return du, cd, src, tags + return du, cd, src } func initDbBolt(boltPath string) (boltDb *bolt.DB, err error) { @@ -73,6 +62,12 @@ func initDbBolt(boltPath string) (boltDb *bolt.DB, err error) { } err = boltDb.Update(func(tx *bolt.Tx) error { + if _, err := tx.CreateBucketIfNotExists([]byte(sourcesFwdBn)); err != nil { + return err + } + if _, err := tx.CreateBucketIfNotExists([]byte(sourcesRevBn)); err != nil { + return err + } if _, err := tx.CreateBucketIfNotExists([]byte(clientDataBn)); err != nil { return err } @@ -97,9 +92,6 @@ func initDbSqlite(sqlitePath string) (dbmap *gorp.DbMap, hubId string, err error dbmap = &gorp.DbMap{Db: db, Dialect: gorp.SqliteDialect{}} // dbmap.TraceOn("[gorp]", log.New(os.Stdout, "myapp:", log.Lmicroseconds)) - dbmap.AddTableWithName(tagDb{}, tagsTn).SetKeys(true, "Id").ColMap("Name").SetUnique(true) - dbmap.AddTableWithName(sourceTagsDb{}, sourceTagsTn).SetKeys(false, "TagId", "SourceId") - dbmap.AddTableWithName(sourceDb{}, sourcesTn).SetKeys(true, "Id").SetUniqueTogether("ContentId", "Format", "Quality", "Hostname") dbmap.AddTableWithName(dataUpdateDb{}, dataUpdatesTn).SetKeys(true, "Id") dbmap.AddTableWithName(hubInfoDb{}, hubInfoTn).SetKeys(false, "Name") @@ -122,24 +114,6 @@ func initDbSqlite(sqlitePath string) (dbmap *gorp.DbMap, hubId string, err error return } -func isEmptyFilter(filter *StatsFilter) bool { - if filter == nil { - return true - } - if filter.start == nil && - filter.end == nil && - filter.hostname == nil && - filter.contentId == nil && - filter.format == nil && - filter.quality == nil && - (filter.tagsAny == nil || len(filter.tagsAny) == 0) && - filter.afterUpdateId == nil && - filter.limit == nil { - return true - } - return false -} - func insertAnd(needsAnd *bool) (res string) { if *needsAnd { res = " and" @@ -148,149 +122,42 @@ func insertAnd(needsAnd *bool) (res string) { return } -type dataUpdateQueryResult struct { - dataUpdateDb - StreamId - SourceId -} - -func getFilteredDataUpdateSelect(filter *StatsFilter) (string, map[string]interface{}) { - const baseQuery = "(select * from " + dataUpdatesTn + "," + sourcesTn + " on " + dataUpdatesTn + ".SourceId = " + sourcesTn + ".Id" - if isEmptyFilter(filter) { - return baseQuery + ")", nil - } - - query := baseQuery - parameters := make(map[string]interface{}) - needsAnd := false - - if filter.start != nil || filter.end != nil || filter.afterUpdateId != nil { - query += " WHERE" - - if filter.start != nil { - query += insertAnd(&needsAnd) - query += " StartTime >= :filterstart" - parameters["filterstart"] = filter.start.Unix() - needsAnd = true - } - if filter.end != nil { - query += insertAnd(&needsAnd) - query += " StartTime < :filterend" - parameters["filterend"] = filter.end.Unix() - needsAnd = true - } - if filter.afterUpdateId != nil { - query += insertAnd(&needsAnd) - query += " " + dataUpdatesTn + ".Id > :afterUpdateId" - parameters["afterUpdateId"] = *filter.afterUpdateId - needsAnd = true - } - } - - if filter.sortOrder != nil { - if *filter.sortOrder == "desc" { - query += " ORDER BY " + dataUpdatesTn + ".Id DESC" - } - } - if filter.limit != nil { - query += " LIMIT :limit" - parameters["limit"] = *filter.limit - } - - // TODO other fields - query += ")" - return query, parameters -} - -func (s sqliteStore) findTag(name string) (tag *tagDb, err error) { - t := tagDb{} - err = s.db.SelectOne(&t, "select * from "+tagsTn+" where Name = ?", name) - if err == nil { - tag = &t - } - return -} - -func (s sqliteStore) insertNewTags(tags []tagDb) (err error) { - for i := range tags { - var t *tagDb - if t, err = s.findTag(tags[i].Name); err == nil { - tags[i] = *t - continue - } - if err = s.db.Insert(&(tags[i])); err != nil { - break - } +func (s sqliteStore) insertDataUpdateEntry(srcId int, du *dataUpdateDb) (err error) { + du.SourceId = srcId + err = s.db.Insert(du) + if err != nil { + return } - return } -func (s sqliteStore) findSource(src sourceDb) (res *sourceDb, err error) { - t := sourceDb{} - err = s.db.SelectOne( - &t, - "select Id from "+sourcesTn+" where ContentId = ? and Format = ? and Quality = ? and Hostname = ?", - src.ContentId, - src.Format, - src.Quality, - src.Hostname) +func (s sqliteStore) insertNewSource(tx *bolt.Tx, src sourceDb) (srcId int, err error) { + bf := tx.Bucket([]byte(sourcesFwdBn)) + bf.FillPercent = 1.0 // we only do appends + br := tx.Bucket([]byte(sourcesRevBn)) + br.FillPercent = 1.0 // we only do appends - if err == nil { - res = &t + slug := src.String() + bSrcId := bf.Get([]byte(slug)) + if bSrcId != nil { + return btoi(bSrcId), nil } - return -} - -func (s sqliteStore) insertNewSource(src *sourceDb) (err error) { - var t *sourceDb - if t, err = s.findSource(*src); err == nil { - *src = *t + var jsonData []byte + if jsonData, err = json.Marshal(src); err != nil { return } - return s.db.Insert(src) -} -func (s sqliteStore) insertSourceTagLinks(src sourceDb, tags []tagDb) (err error) { - st := make([]sourceTagsDb, len(tags)) - for i := range tags { - st[i].TagId = tags[i].Id - st[i].SourceId = src.Id - } - for i := range st { - _, err = s.db.Exec( - "insert or ignore into "+sourceTagsTn+" values (?,?)", - st[i].TagId, - st[i].SourceId) - // err = s.db.Insert(&st[i]) - if err != nil { - // TODO - //fmt.Printf("st\n") - return - } + next, _ := bf.NextSequence() + srcId = int(next) + if err = bf.Put([]byte(slug), itob(srcId)); err != nil { + return } - return -} - -func (s sqliteStore) insertDataUpdateEntry(src sourceDb, du *dataUpdateDb) (err error) { - du.SourceId = src.Id - err = s.db.Insert(du) - if err != nil { - //fmt.Printf("du\n") + if err = br.Put(itob(srcId), jsonData); err != nil { return } - return -} -func itob(v int) []byte { - b := make([]byte, 8) - binary.BigEndian.PutUint64(b, uint64(v)) - return b -} - -func btoi(b []byte) int { - return int(binary.BigEndian.Uint64(b)) + return srcId, err } func (s sqliteStore) insertNewUserAgent(tx *bolt.Tx, ua string) (uaId int, err error) { @@ -316,51 +183,41 @@ func (s sqliteStore) insertNewUserAgent(tx *bolt.Tx, ua string) (uaId int, err e return uaId, err } -func (s sqliteStore) insertDataUpdateClientEntries(cd []ClientData, du dataUpdateDb) error { +func (s sqliteStore) insertDataUpdateClientEntries(tx *bolt.Tx, duId int, cd []ClientData) error { if len(cd) == 0 { return nil } - return s.dbBolt.Update(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte(clientDataBn)) - b.FillPercent = 1.0 // we only do appends - - data := []clientDataDb{} - for _, c := range cd { - uaId, err := s.insertNewUserAgent(tx, c.UserAgent) - if err != nil { - return err - } - data = append(data, clientDataDb{c.Ip, uaId, c.BytesSent}) - } + b := tx.Bucket([]byte(clientDataBn)) + b.FillPercent = 1.0 // we only do appends - jsonData, err := json.Marshal(data) + data := []clientDataDb{} + for _, c := range cd { + uaId, err := s.insertNewUserAgent(tx, c.UserAgent) if err != nil { return err } - return b.Put(itob(du.Id), jsonData) - }) -} - -func (s sqliteStore) appendItem(du dataUpdateDb, cd []ClientData, src sourceDb, tags []tagDb) (err error) { - if err = s.insertNewTags(tags); err != nil { - return + data = append(data, clientDataDb{c.Ip, uaId, c.BytesSent}) } - if err = s.insertNewSource(&src); err != nil { - //fmt.Printf("src\n") - return + jsonData, err := json.Marshal(data) + if err != nil { + return err } + return b.Put(itob(duId), jsonData) +} - if err = s.insertSourceTagLinks(src, tags); err != nil { +func (s sqliteStore) appendItem(tx *bolt.Tx, du dataUpdateDb, cd []ClientData, src sourceDb) (err error) { + var srcId int + if srcId, err = s.insertNewSource(tx, src); err != nil { return } - if err = s.insertDataUpdateEntry(src, &du); err != nil { + if err = s.insertDataUpdateEntry(srcId, &du); err != nil { return } - if err = s.insertDataUpdateClientEntries(cd, du); err != nil { + if err = s.insertDataUpdateClientEntries(tx, du.Id, cd); err != nil { return } @@ -378,63 +235,66 @@ func (s sqliteStore) AppendMany(updates []StatisticsData) (err error) { return } - for _, update := range updates { - du, cd, src, tags := updateFromStatisticsData(update) - err = s.appendItem(du, cd, src, tags) - if err != nil { - tx.Rollback() - return + err = s.dbBolt.Update(func(tx *bolt.Tx) error { + for _, update := range updates { + du, cd, src := updateFromStatisticsData(update) + if err := s.appendItem(tx, du, cd, src); err != nil { + return err + } } + return nil + }) + + if err != nil { + tx.Rollback() + return } return tx.Commit() } -func castArrayToString(value []interface{}) []string { - res := make([]string, len(value)) - for i := range value { - res[i] = value[i].(*tagDb).Name - } - return res -} +func (s sqliteStore) getSource(tx *bolt.Tx, id int) (res sourceDb, err error) { + b := tx.Bucket([]byte(sourcesRevBn)) -func (s sqliteStore) GetTags() ([]string, error) { - res, dbErr := s.db.Select(tagDb{}, "select Name from "+tagsTn) - if dbErr == nil { - sRes := castArrayToString(res) - return sRes, nil + jsonData := b.Get(itob(id)) + if jsonData == nil { + err = ErrNotFound + return } - return nil, dbErr -} - -func (s sqliteStore) GetTagsByDataUpdateId(id int) (res []string, err error) { - var qres []interface{} - qres, err = s.db.Select( - tagDb{}, - "select * from "+tagsTn+" where Id in (select TagId from "+sourceTagsTn+" where SourceId = (select SourceId from "+dataUpdatesTn+" where Id = ?))", id) - if err == nil { - res = make([]string, len(qres)) - for i := range qres { - res[i] = qres[i].(*tagDb).Name - } + if err = json.Unmarshal(jsonData, &res); err != nil { + return } return } -func (s sqliteStore) GetSources() (res []sourceDb, err error) { - var qres []interface{} - qres, err = s.db.Select(sourceDb{}, "select * from "+sourcesTn) - if err == nil { - res = make([]sourceDb, len(qres)) - for i := range qres { - res[i] = *qres[i].(*sourceDb) +func (s sqliteStore) GetSources() (res []SourceId, err error) { + res = []SourceId{} + err = s.dbBolt.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(sourcesRevBn)) + c := b.Cursor() + for k, v := c.First(); k != nil; k, v = c.Next() { + var s sourceDb + if err := json.Unmarshal(v, &s); err != nil { + return err + } + var src SourceId + src.CopyFromSourceDb(s) + res = append(res, src) } - } + return nil + }) return } -func (s sqliteStore) GetSource(id int) (res sourceDb, err error) { - err = s.db.SelectOne(&res, "select * from "+sourcesTn+" where Id = ?", id) +func (s sqliteStore) GetSource(id int) (res SourceId, err error) { + err = s.dbBolt.View(func(tx *bolt.Tx) error { + src, err := s.getSource(tx, id) + if err != nil { + return err + } + res.CopyFromSourceDb(src) + return nil + }) return } @@ -443,86 +303,70 @@ func (s sqliteStore) GetUpdate(id int) (res dataUpdateDb, err error) { return } -func (s sqliteStore) GetClientsByUpdateId(id int) (res []ClientData, err error) { - err = s.dbBolt.View(func(tx *bolt.Tx) error { - bc := tx.Bucket([]byte(clientDataBn)) - bu := tx.Bucket([]byte(userAgentsRevBn)) +func (s sqliteStore) getClientsByUpdateId(tx *bolt.Tx, id int) (res []ClientData, err error) { + bc := tx.Bucket([]byte(clientDataBn)) + bu := tx.Bucket([]byte(userAgentsRevBn)) - jsonData := bc.Get(itob(id)) - if jsonData == nil { - return nil - } - data := []clientDataDb{} - if err := json.Unmarshal(jsonData, &data); err != nil { - return err - } - for _, c := range data { - cd := ClientData{Ip: c.Ip, BytesSent: c.BytesSent} - ua := bu.Get(itob(c.UserAgentId)) - if ua != nil { - cd.UserAgent = string(ua) - } - res = append(res, cd) + jsonData := bc.Get(itob(id)) + if jsonData == nil { + return + } + data := []clientDataDb{} + if err = json.Unmarshal(jsonData, &data); err != nil { + return + } + for _, c := range data { + cd := ClientData{Ip: c.Ip, BytesSent: c.BytesSent} + ua := bu.Get(itob(c.UserAgentId)) + if ua != nil { + cd.UserAgent = string(ua) } - return nil - }) + res = append(res, cd) + } return } -var ( - updateColumnSelect = ` - Id, - SourceHubUuid, - SourceHubDataUpdateId, - StartTime, - Duration, - ClientCount, - BytesReceived, - BytesSent, - Hostname, - ContentId, - Format, - Quality -` -) - -func (s sqliteStore) CreateStatisticsDataFrom(dat dataUpdateQueryResult) (res StatisticsData, err error) { +func (s sqliteStore) CreateStatisticsDataFrom(tx *bolt.Tx, dat dataUpdateDb) (res StatisticsData, err error) { var clients []ClientData - clients, err = s.GetClientsByUpdateId(dat.Id) - if err != nil { - s5l.Printf("store GetClients failed: %v", err) + if clients, err = s.getClientsByUpdateId(tx, dat.Id); err != nil { return } - tagsDb, err := s.GetTagsByDataUpdateId(dat.Id) - if err != nil { - s5l.Printf("store GetClients failed: %v", err) + var src sourceDb + if src, err = s.getSource(tx, dat.SourceId); err != nil { return } - res.CopyFromDataUpdateDb(dat.dataUpdateDb, s.hubId) - res.Hostname = dat.Hostname - res.StreamId.ContentId = dat.ContentId - res.StreamId.Format = dat.Format - res.StreamId.Quality = dat.Quality + + res.CopyFromDataUpdateDb(dat, s.hubId) + res.Hostname = src.Hostname + res.StreamId.ContentId = src.StreamId.ContentId + res.StreamId.Format = src.StreamId.Format + res.StreamId.Quality = src.StreamId.Quality + res.Tags = src.Tags res.Data.Clients = clients - res.Tags = tagsDb return } func (s sqliteStore) CreateStatisticsDatasFrom(dat []interface{}) (res []StatisticsData, err error) { - res = make([]StatisticsData, len(dat)) - for i := range dat { - t := *dat[i].(*dataUpdateQueryResult) - res[i], _ = s.CreateStatisticsDataFrom(t) - } + err = s.dbBolt.View(func(tx *bolt.Tx) error { + for i := range dat { + sd, err := s.CreateStatisticsDataFrom(tx, *dat[i].(*dataUpdateDb)) + if err != nil { + return err + } + res = append(res, sd) + } + return nil + }) return } func (s sqliteStore) GetUpdatesAfter(id int) (res []StatisticsData, err error) { - limit := 5000 - sourceSql, parameters := getFilteredDataUpdateSelect(&StatsFilter{afterUpdateId: &id, limit: &limit}) - sql := "SELECT " + updateColumnSelect + " FROM " + sourceSql + parameters := make(map[string]interface{}) + sql := "SELECT * FROM " + dataUpdatesTn + " WHERE Id > :afterUpdateId limit :limit" + parameters["afterUpdateId"] = id + parameters["limit"] = 5000 var updates []interface{} - updates, err = s.db.Select(dataUpdateQueryResult{}, sql, parameters) + updates, err = s.db.Select(dataUpdateDb{}, sql, parameters) s5tl.Printf("sql: %s", sql) if err == nil { res, _ = s.CreateStatisticsDatasFrom(updates) @@ -531,21 +375,23 @@ func (s sqliteStore) GetUpdatesAfter(id int) (res []StatisticsData, err error) { } func (s sqliteStore) GetUpdates(filter *StatsFilter) (res []StatisticsData, err error) { - limit := 5000 - if filter.limit == nil { - filter.limit = &limit - } else if *filter.limit > limit { - *filter.limit = limit - } - sourceSql, parameters := getFilteredDataUpdateSelect(filter) - sql := "SELECT " + updateColumnSelect + " FROM " + sourceSql - s5tl.Printf("store: sql: %s", sql) - var updates []interface{} - updates, err = s.db.Select(dataUpdateQueryResult{}, sql, parameters) - if err == nil { - res, _ = s.CreateStatisticsDatasFrom(updates) - } + err = fmt.Errorf("not implemented!") return + // limit := 5000 + // if filter.limit == nil { + // filter.limit = &limit + // } else if *filter.limit > limit { + // *filter.limit = limit + // } + // sourceSql, parameters := getFilteredDataUpdateSelect(filter) + // sql := "SELECT " + updateColumnSelect + " FROM " + sourceSql + // s5tl.Printf("store: sql: %s", sql) + // var updates []interface{} + // updates, err = s.db.Select(dataUpdateQueryResult{}, sql, parameters) + // if err == nil { + // res, _ = s.CreateStatisticsDatasFrom(updates) + // } + // return } type lastUpdateQueryResult struct { @@ -627,48 +473,49 @@ func toApiStatsResult(value statsResult) (res StatsResult) { return res } -var ( - statsGroupSelect = ` -SELECT - count(*) as UpdateCount, - SourceHubUuid as SourceHubUuid, - count(distinct SourceId) as SourcesCount, - avg(ClientCount) as ClientCount, - sum(BytesSent) as BytesSent, - sum(BytesReceived) as BytesReceived, - min(StartTime) as StartTime, - max(StartTime) as LastStartTime -FROM -` - statsGroupClause = ` -GROUP BY - SourceHubUuid -` - statsAggregateSelect = ` -SELECT - sum(UpdateCount) as UpdateCount, - count(distinct SourceHubUuid) as HubCount, - sum(SourcesCount) as SourcesCount, - sum(ClientCount) as ClientCount, - sum(BytesSent) as BytesSent, - sum(BytesReceived) as BytesReceived, - min(StartTime) as StartTime, - max(LastStartTime) as LastStartTime -FROM -` -) +// var ( +// statsGroupSelect = ` +// SELECT +// count(*) as UpdateCount, +// SourceHubUuid as SourceHubUuid, +// count(distinct SourceId) as SourcesCount, +// avg(ClientCount) as ClientCount, +// sum(BytesSent) as BytesSent, +// sum(BytesReceived) as BytesReceived, +// min(StartTime) as StartTime, +// max(StartTime) as LastStartTime +// FROM +// ` +// statsGroupClause = ` +// GROUP BY +// SourceHubUuid +// ` +// statsAggregateSelect = ` +// SELECT +// sum(UpdateCount) as UpdateCount, +// count(distinct SourceHubUuid) as HubCount, +// sum(SourcesCount) as SourcesCount, +// sum(ClientCount) as ClientCount, +// sum(BytesSent) as BytesSent, +// sum(BytesReceived) as BytesReceived, +// min(StartTime) as StartTime, +// max(LastStartTime) as LastStartTime +// FROM +// ` +// ) func (s sqliteStore) GetStats(filter *StatsFilter) (StatsResult, error) { // (map[string]interface{}, error) { - sourceSql, parameters := getFilteredDataUpdateSelect(filter) - _ = sourceSql - sql := fmt.Sprintf("%s (%s %s %s)", statsAggregateSelect, statsGroupSelect, sourceSql, statsGroupClause) - s5tl.Printf("store: stats sql: %s", sql) - res := statsResult{} - err := s.db.SelectOne(&res, sql, parameters) - if err == nil { - return toApiStatsResult(res), nil - } - return StatsResult{}, err + return StatsResult{}, fmt.Errorf("not implemented!") + // sourceSql, parameters := getFilteredDataUpdateSelect(filter) + // _ = sourceSql + // sql := fmt.Sprintf("%s (%s %s %s)", statsAggregateSelect, statsGroupSelect, sourceSql, statsGroupClause) + // s5tl.Printf("store: stats sql: %s", sql) + // res := statsResult{} + // err := s.db.SelectOne(&res, sql, parameters) + // if err == nil { + // return toApiStatsResult(res), nil + // } + // return StatsResult{}, err } func (s sqliteStore) GetStoreId() (uuid string, err error) { diff --git a/src/hub/src/spreadspace.org/sfive/s5store_test.go b/src/hub/src/spreadspace.org/sfive/s5store_test.go index 92ad149..27732a5 100644 --- a/src/hub/src/spreadspace.org/sfive/s5store_test.go +++ b/src/hub/src/spreadspace.org/sfive/s5store_test.go @@ -43,31 +43,31 @@ func TestAppend(t *testing.T) { return } - stats, err := store.GetStats(nil) - if err != nil { - t.Errorf("Failed to get stats: %v", err) - } else { - clientCount := int(stats.ClientCount) - updateCount := stats.UpdateCount - if 3 != clientCount { - t.Errorf("Failed fo append, invalid number of clients, 3 != %v", clientCount) - } - if 1 != updateCount { - t.Errorf("Failed to append, invalid number of updates, 1 != %v", updateCount) - } - } - - queryStartTime := time.Date(2015, time.December, 24, 1, 1, 1, 0, time.UTC) - filterStruct := StatsFilter{start: &queryStartTime} - stats, err = store.GetStats(&filterStruct) - if err != nil { - t.Errorf("Failed to get stats: %v", err) - } else { - updateCount := stats.UpdateCount - if 0 != updateCount { - t.Errorf("Failed to filter entries by start time, 0 != %v", updateCount) - } - } + // stats, err := store.GetStats(nil) + // if err != nil { + // t.Errorf("Failed to get stats: %v", err) + // } else { + // clientCount := int(stats.ClientCount) + // updateCount := stats.UpdateCount + // if 3 != clientCount { + // t.Errorf("Failed fo append, invalid number of clients, 3 != %v", clientCount) + // } + // if 1 != updateCount { + // t.Errorf("Failed to append, invalid number of updates, 1 != %v", updateCount) + // } + // } + + // queryStartTime := time.Date(2015, time.December, 24, 1, 1, 1, 0, time.UTC) + // filterStruct := StatsFilter{start: &queryStartTime} + // stats, err = store.GetStats(&filterStruct) + // if err != nil { + // t.Errorf("Failed to get stats: %v", err) + // } else { + // updateCount := stats.UpdateCount + // if 0 != updateCount { + // t.Errorf("Failed to filter entries by start time, 0 != %v", updateCount) + // } + // } } func TestCount(t *testing.T) { diff --git a/src/hub/src/spreadspace.org/sfive/s5typesStore.go b/src/hub/src/spreadspace.org/sfive/s5typesStore.go index a177aea..a6ac3a9 100644 --- a/src/hub/src/spreadspace.org/sfive/s5typesStore.go +++ b/src/hub/src/spreadspace.org/sfive/s5typesStore.go @@ -1,21 +1,28 @@ package sfive import ( + "encoding/binary" + "errors" + "fmt" + "strings" "time" ) +var ( + ErrNotFound = errors.New("not found") +) + // compared to JSON DTOs, DB types are flattened, and use key-relations instead of collections // this is very much not normalized at all, because I'm too lazy to type const ( // sqlite table names - tagsTn = "Tags" - sourceTagsTn = "StreamToTagMap" - sourcesTn = "Sources" dataUpdatesTn = "DataUpdates" hubInfoTn = "HubInfo" // bolt bucket names + sourcesFwdBn = "SourcesFwd" + sourcesRevBn = "SourcesRev" clientDataBn = "ClientData" userAgentsFwdBn = "UserAgentsFwd" userAgentsRevBn = "UserAgentsRev" @@ -26,24 +33,29 @@ type hubInfoDb struct { Value string } -// stored in tagsTn -type tagDb struct { - Id int - Name string +// stored in sourcesRevBn +type streamIdDb struct { + ContentId string `json:"c"` + Format string `json:"f"` + Quality string `json:"q"` } -// stored in sourceTagsTn -// Stream m:n Tag -type sourceTagsDb struct { - TagId int // foreign key to tagsTn - SourceId int // foreign key to sourcesTn +type sourceDb struct { + Hostname string `json:"h"` + StreamId streamIdDb `json:"s"` + Tags []string `json:"t"` } -// stored in sourcesTn -type sourceDb struct { - Id int - StreamId - SourceId +func (s sourceDb) String() string { + return fmt.Sprintf("%s/%s/%s/%s/%s", s.Hostname, s.StreamId.ContentId, s.StreamId.Format, s.StreamId.Quality, strings.Join(s.Tags, ",")) +} + +func (s *SourceId) CopyFromSourceDb(v sourceDb) { + s.Hostname = v.Hostname + s.StreamId.ContentId = v.StreamId.ContentId + s.StreamId.Format = v.StreamId.Format + s.StreamId.Quality = v.StreamId.Quality + s.Tags = v.Tags } // stored in clientDataBn @@ -67,47 +79,31 @@ type dataUpdateDb struct { BytesSent uint } -func (self *SourceId) CopyFromSourceDb(value sourceDb) { - self.Version = value.Version - self.Hostname = value.Hostname - self.StreamId.ContentId = value.ContentId - self.StreamId.Format = value.Format - self.StreamId.Quality = value.Quality -} - -func (self *SourceId) CopyFromTagsDb(values []tagDb) { - tags := make([]string, len(values)) - for i := range values { - tags[i] = values[i].Name - } - self.Tags = tags -} - -func (self *StatisticsData) CopyFromDataUpdateDb(value dataUpdateDb, hubId string) { - if value.SourceHubUuid == nil { - self.SourceHubUuid = &hubId +func (s *StatisticsData) CopyFromDataUpdateDb(v dataUpdateDb, hubId string) { + if v.SourceHubUuid == nil { + s.SourceHubUuid = &hubId } else { - self.SourceHubUuid = value.SourceHubUuid + s.SourceHubUuid = v.SourceHubUuid } - if value.SourceHubDataUpdateId == nil { - self.SourceHubDataUpdateId = &value.Id + if v.SourceHubDataUpdateId == nil { + s.SourceHubDataUpdateId = &v.Id } else { - self.SourceHubDataUpdateId = value.SourceHubDataUpdateId + s.SourceHubDataUpdateId = v.SourceHubDataUpdateId } - self.StartTime = time.Unix(value.StartTime, 0) - self.Duration = value.Duration - self.Data.ClientCount = value.ClientCount - self.Data.BytesReceived = value.BytesReceived - self.Data.BytesSent = value.BytesSent + s.StartTime = time.Unix(v.StartTime, 0) + s.Duration = v.Duration + s.Data.ClientCount = v.ClientCount + s.Data.BytesReceived = v.BytesReceived + s.Data.BytesSent = v.BytesSent +} + +func itob(v int) []byte { + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, uint64(v)) + return b } -func cvtToApiStatisticsData( - hubId string, source sourceDb, update dataUpdateDb, clients []ClientData, tags []tagDb) StatisticsData { - res := StatisticsData{} - res.CopyFromSourceDb(source) - res.CopyFromDataUpdateDb(update, hubId) - res.Data.Clients = clients - res.CopyFromTagsDb(tags) - return res +func btoi(b []byte) int { + return int(binary.BigEndian.Uint64(b)) } -- cgit v1.2.3 From 1461c1f8588809c5dd7c52ca92c05ea7eb6e526e Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Thu, 27 Apr 2017 01:12:24 +0200 Subject: remove most of no-longer-needed stats stuff --- src/hub/src/spreadspace.org/sfive/s5cvt.go | 15 --- src/hub/src/spreadspace.org/sfive/s5srv.go | 31 +----- src/hub/src/spreadspace.org/sfive/s5srvWeb.go | 85 +-------------- src/hub/src/spreadspace.org/sfive/s5store.go | 126 +--------------------- src/hub/src/spreadspace.org/sfive/s5store_test.go | 41 ------- src/hub/src/spreadspace.org/sfive/s5typesApi.go | 13 --- src/hub/test-client | 13 +-- 7 files changed, 12 insertions(+), 312 deletions(-) (limited to 'src') diff --git a/src/hub/src/spreadspace.org/sfive/s5cvt.go b/src/hub/src/spreadspace.org/sfive/s5cvt.go index cd65cf7..a8ea4f8 100644 --- a/src/hub/src/spreadspace.org/sfive/s5cvt.go +++ b/src/hub/src/spreadspace.org/sfive/s5cvt.go @@ -13,10 +13,6 @@ type StatsEncoder interface { Encode(data StatisticsData) []byte } -type FilterDecoder interface { - Decode(jsonString []byte) (StatsFilter, error) -} - type StatefulDecoder struct { sourceId SourceId } @@ -25,8 +21,6 @@ type PlainDecoder struct{} type PlainEncoder struct{} -type filterDecoder struct{} - func NewStatefulDecoder(jsonString []byte) (decoder StatsDecoder, err error) { res := new(StatefulDecoder) err = json.Unmarshal(jsonString, &res.sourceId) @@ -44,10 +38,6 @@ func NewPlainDecoder() StatsDecoder { return new(PlainDecoder) } -func NewFilterDecoder() FilterDecoder { - return new(filterDecoder) -} - func (self *StatefulDecoder) Decode(jsonString []byte) (dat StatisticsData, err error) { dat.CopyFromSourceId(&self.sourceId) err = json.Unmarshal(jsonString, &dat) @@ -68,8 +58,3 @@ func (self *PlainEncoder) Encode(data *StatisticsData) []byte { } return res } - -func (self *filterDecoder) Decode(jsonString []byte) (dat StatsFilter, err error) { - err = json.Unmarshal(jsonString, &dat) - return -} diff --git a/src/hub/src/spreadspace.org/sfive/s5srv.go b/src/hub/src/spreadspace.org/sfive/s5srv.go index 3dbd6e7..a69adfe 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srv.go +++ b/src/hub/src/spreadspace.org/sfive/s5srv.go @@ -10,16 +10,6 @@ type appendManyToken struct { response chan bool } -type queryStatsResult struct { - stats StatsResult - err error -} - -type queryStatsToken struct { - filter *StatsFilter - response chan queryStatsResult -} - type getUpdatesResult struct { values []StatisticsData err error @@ -31,7 +21,6 @@ type getUpdatesAfterToken struct { } type getUpdatesToken struct { - filter *StatsFilter response chan getUpdatesResult } @@ -59,7 +48,6 @@ type StatsSinkServer struct { done chan bool appendData chan StatisticsData appendManyData chan appendManyToken // chan []StatisticsData - getStatsChan chan queryStatsToken getUpdatesAfterChan chan getUpdatesAfterToken getUpdatesChan chan getUpdatesToken getHubIdChan chan getHubIdToken @@ -93,14 +81,11 @@ func (self StatsSinkServer) appendActor() { } else { token.response <- true } - case token := <-self.getStatsChan: - stats, err := self.store.GetStats(token.filter) - token.response <- queryStatsResult{stats, err} case token := <-self.getUpdatesAfterChan: values, err := self.store.GetUpdatesAfter(token.id) token.response <- getUpdatesResult{values, err} case token := <-self.getUpdatesChan: - values, err := self.store.GetUpdates(token.filter) + values, err := self.store.GetUpdates() token.response <- getUpdatesResult{values, err} case token := <-self.getHubIdChan: storeId, err := self.store.GetStoreId() @@ -124,22 +109,14 @@ func (self StatsSinkServer) getUpdatesAfterInvoke(id int) ([]StatisticsData, err return res.values, res.err } -func (self StatsSinkServer) getUpdatesInvoke(filter *StatsFilter) ([]StatisticsData, error) { - token := getUpdatesToken{filter: filter, response: make(chan getUpdatesResult, 1)} +func (self StatsSinkServer) getUpdatesInvoke() ([]StatisticsData, error) { + token := getUpdatesToken{response: make(chan getUpdatesResult, 1)} defer close(token.response) self.getUpdatesChan <- token res := <-token.response return res.values, res.err } -func (self StatsSinkServer) getStatsInvoke(filter *StatsFilter) (StatsResult, error) { - token := queryStatsToken{filter: filter, response: make(chan queryStatsResult, 1)} - defer close(token.response) - self.getStatsChan <- token - res := <-token.response - return res.stats, res.err -} - func (self StatsSinkServer) getHubIdInvoke() (string, error) { token := getHubIdToken{response: make(chan getHubIdResult, 1)} defer close(token.response) @@ -163,7 +140,6 @@ func (self StatsSinkServer) Close() { close(self.done) close(self.appendData) close(self.appendManyData) - close(self.getStatsChan) close(self.getUpdatesAfterChan) close(self.getUpdatesChan) close(self.getHubIdChan) @@ -186,7 +162,6 @@ func NewServer(dbPath string) (server *StatsSinkServer, err error) { server.done = make(chan bool) server.appendData = make(chan StatisticsData, 5) server.appendManyData = make(chan appendManyToken, 5) - server.getStatsChan = make(chan queryStatsToken, 5) server.getUpdatesAfterChan = make(chan getUpdatesAfterToken, 1) server.getUpdatesChan = make(chan getUpdatesToken, 3) server.getHubIdChan = make(chan getHubIdToken, 1) diff --git a/src/hub/src/spreadspace.org/sfive/s5srvWeb.go b/src/hub/src/spreadspace.org/sfive/s5srvWeb.go index aa98532..54daa65 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvWeb.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvWeb.go @@ -7,7 +7,6 @@ import ( "net/http" "os" "strconv" - "time" "github.com/zenazn/goji" "github.com/zenazn/goji/web" @@ -57,73 +56,9 @@ func (self StatsSinkServer) getSource(c web.C, w http.ResponseWriter, r *http.Re fmt.Fprintf(w, "%s", jsonString) } -func getFilter(r *http.Request) (filter StatsFilter) { - from := r.FormValue("from") - if from != "" { - fromT, err := time.Parse(time.RFC3339, from) - if err == nil { - filter.start = &fromT - } - } - - to := r.FormValue("to") - if to != "" { - toT, err := time.Parse(time.RFC3339, to) - if err == nil { - filter.end = &toT - } - } - - hostname := r.FormValue("hostname") - if hostname != "" { - filter.hostname = &hostname - } - - contentId := r.FormValue("contentId") - if contentId != "" { - filter.contentId = &contentId - } - - format := r.FormValue("format") - if format != "" { - filter.format = &format - } - - quality := r.FormValue("quality") - if quality != "" { - filter.quality = &quality - } - - afterUpdateId := r.FormValue("afterUpdateId") - if afterUpdateId != "" { - id, err := strconv.ParseInt(afterUpdateId, 10, 32) - if err == nil { - idInt := int(id) - filter.afterUpdateId = &idInt - } - } - - limit := r.FormValue("limit") - if limit != "" { - limitInt, err := strconv.ParseInt(limit, 10, 32) - if err == nil { - limitIntInt := int(limitInt) - filter.limit = &limitIntInt - } - } - - sortOrder := r.FormValue("sortOrder") - if sortOrder != "" { - filter.sortOrder = &sortOrder - } - - return -} - func (self StatsSinkServer) getUpdateList(c web.C, w http.ResponseWriter, r *http.Request) { const resourceName = "updates" - filter := getFilter(r) - values, err := self.getUpdatesInvoke(&filter) + values, err := self.getUpdatesInvoke() if err != nil { http.Error(w, fmt.Sprintf("failed to retrieve %s: %v", resourceName, err), http.StatusInternalServerError) return @@ -207,23 +142,6 @@ func (self StatsSinkServer) getLastUpdateIdForUuid(c web.C, w http.ResponseWrite } } -func (self StatsSinkServer) getStats(c web.C, w http.ResponseWriter, r *http.Request) { - const resourceName = "stats" - filter := getFilter(r) - values, err := self.getStatsInvoke(&filter) - - if err != nil { - http.Error(w, fmt.Sprintf("failed to retrieve %s: %v", resourceName, err), http.StatusInternalServerError) - return - } - jsonString, err := json.Marshal(values) - if err != nil { - http.Error(w, fmt.Sprintf("failed to marshal %s: %v", resourceName, err), http.StatusInternalServerError) - return - } - fmt.Fprintf(w, "%s", jsonString) -} - func (self StatsSinkServer) ServeWeb(vizAppLocation string) { if _, err := os.Stat(vizAppLocation); err != nil { if os.IsNotExist(err) { @@ -240,7 +158,6 @@ func (self StatsSinkServer) ServeWeb(vizAppLocation string) { goji.Get("/updates/:id", self.getUpdate) goji.Post("/updates", self.postUpdate) goji.Get("/lastupdate/:id", self.getLastUpdateIdForUuid) - goji.Get("/stats", self.getStats) goji.Handle("/viz/*", http.StripPrefix("/viz/", http.FileServer(http.Dir(vizAppLocation)))) goji.Serve() diff --git a/src/hub/src/spreadspace.org/sfive/s5store.go b/src/hub/src/spreadspace.org/sfive/s5store.go index 07a73e9..c4e1676 100644 --- a/src/hub/src/spreadspace.org/sfive/s5store.go +++ b/src/hub/src/spreadspace.org/sfive/s5store.go @@ -3,7 +3,6 @@ package sfive import ( "database/sql" "encoding/json" - "fmt" "time" // needed for gorp tracing @@ -114,14 +113,6 @@ func initDbSqlite(sqlitePath string) (dbmap *gorp.DbMap, hubId string, err error return } -func insertAnd(needsAnd *bool) (res string) { - if *needsAnd { - res = " and" - *needsAnd = false - } - return -} - func (s sqliteStore) insertDataUpdateEntry(srcId int, du *dataUpdateDb) (err error) { du.SourceId = srcId err = s.db.Insert(du) @@ -364,7 +355,7 @@ func (s sqliteStore) GetUpdatesAfter(id int) (res []StatisticsData, err error) { parameters := make(map[string]interface{}) sql := "SELECT * FROM " + dataUpdatesTn + " WHERE Id > :afterUpdateId limit :limit" parameters["afterUpdateId"] = id - parameters["limit"] = 5000 + parameters["limit"] = 5000 // TODO: hardcoded value var updates []interface{} updates, err = s.db.Select(dataUpdateDb{}, sql, parameters) s5tl.Printf("sql: %s", sql) @@ -374,24 +365,8 @@ func (s sqliteStore) GetUpdatesAfter(id int) (res []StatisticsData, err error) { return } -func (s sqliteStore) GetUpdates(filter *StatsFilter) (res []StatisticsData, err error) { - err = fmt.Errorf("not implemented!") - return - // limit := 5000 - // if filter.limit == nil { - // filter.limit = &limit - // } else if *filter.limit > limit { - // *filter.limit = limit - // } - // sourceSql, parameters := getFilteredDataUpdateSelect(filter) - // sql := "SELECT " + updateColumnSelect + " FROM " + sourceSql - // s5tl.Printf("store: sql: %s", sql) - // var updates []interface{} - // updates, err = s.db.Select(dataUpdateQueryResult{}, sql, parameters) - // if err == nil { - // res, _ = s.CreateStatisticsDatasFrom(updates) - // } - // return +func (s sqliteStore) GetUpdates() (res []StatisticsData, err error) { + return s.GetUpdatesAfter(-1) } type lastUpdateQueryResult struct { @@ -423,101 +398,6 @@ func (s sqliteStore) GetLastUpdateId() (updateId *int, err error) { return } -type statsResult struct { - UpdateCount *int - HubCount *int - SourcesCount *int - ClientCount *float32 - BytesSent *uint - BytesReceived *uint - StartTime *int64 - LastStartTime *int64 -} - -type StatsResult struct { - UpdateCount int - HubCount int - SourcesCount int - ClientCount float32 - BytesSent uint - BytesReceived uint - StartTime time.Time - LastStartTime time.Time -} - -func toApiStatsResult(value statsResult) (res StatsResult) { - if value.UpdateCount != nil { - res.UpdateCount = *value.UpdateCount - } - if value.HubCount != nil { - res.HubCount = *value.HubCount - } - if value.SourcesCount != nil { - res.SourcesCount = *value.SourcesCount - } - if value.ClientCount != nil { - res.ClientCount = *value.ClientCount - } - if value.BytesSent != nil { - res.BytesSent = *value.BytesSent - } - if value.BytesReceived != nil { - res.BytesReceived = *value.BytesReceived - } - if value.StartTime != nil { - res.StartTime = time.Unix(*value.StartTime, 0) - } - if value.LastStartTime != nil { - res.LastStartTime = time.Unix(*value.LastStartTime, 0) - } - return res -} - -// var ( -// statsGroupSelect = ` -// SELECT -// count(*) as UpdateCount, -// SourceHubUuid as SourceHubUuid, -// count(distinct SourceId) as SourcesCount, -// avg(ClientCount) as ClientCount, -// sum(BytesSent) as BytesSent, -// sum(BytesReceived) as BytesReceived, -// min(StartTime) as StartTime, -// max(StartTime) as LastStartTime -// FROM -// ` -// statsGroupClause = ` -// GROUP BY -// SourceHubUuid -// ` -// statsAggregateSelect = ` -// SELECT -// sum(UpdateCount) as UpdateCount, -// count(distinct SourceHubUuid) as HubCount, -// sum(SourcesCount) as SourcesCount, -// sum(ClientCount) as ClientCount, -// sum(BytesSent) as BytesSent, -// sum(BytesReceived) as BytesReceived, -// min(StartTime) as StartTime, -// max(LastStartTime) as LastStartTime -// FROM -// ` -// ) - -func (s sqliteStore) GetStats(filter *StatsFilter) (StatsResult, error) { // (map[string]interface{}, error) { - return StatsResult{}, fmt.Errorf("not implemented!") - // sourceSql, parameters := getFilteredDataUpdateSelect(filter) - // _ = sourceSql - // sql := fmt.Sprintf("%s (%s %s %s)", statsAggregateSelect, statsGroupSelect, sourceSql, statsGroupClause) - // s5tl.Printf("store: stats sql: %s", sql) - // res := statsResult{} - // err := s.db.SelectOne(&res, sql, parameters) - // if err == nil { - // return toApiStatsResult(res), nil - // } - // return StatsResult{}, err -} - func (s sqliteStore) GetStoreId() (uuid string, err error) { uuid, err = s.db.SelectStr("select Value from HubInfo where Name = ?", "HubUuid") return diff --git a/src/hub/src/spreadspace.org/sfive/s5store_test.go b/src/hub/src/spreadspace.org/sfive/s5store_test.go index 27732a5..fe43bf5 100644 --- a/src/hub/src/spreadspace.org/sfive/s5store_test.go +++ b/src/hub/src/spreadspace.org/sfive/s5store_test.go @@ -42,47 +42,6 @@ func TestAppend(t *testing.T) { t.Errorf("Failed to append: %v", err) return } - - // stats, err := store.GetStats(nil) - // if err != nil { - // t.Errorf("Failed to get stats: %v", err) - // } else { - // clientCount := int(stats.ClientCount) - // updateCount := stats.UpdateCount - // if 3 != clientCount { - // t.Errorf("Failed fo append, invalid number of clients, 3 != %v", clientCount) - // } - // if 1 != updateCount { - // t.Errorf("Failed to append, invalid number of updates, 1 != %v", updateCount) - // } - // } - - // queryStartTime := time.Date(2015, time.December, 24, 1, 1, 1, 0, time.UTC) - // filterStruct := StatsFilter{start: &queryStartTime} - // stats, err = store.GetStats(&filterStruct) - // if err != nil { - // t.Errorf("Failed to get stats: %v", err) - // } else { - // updateCount := stats.UpdateCount - // if 0 != updateCount { - // t.Errorf("Failed to filter entries by start time, 0 != %v", updateCount) - // } - // } -} - -func TestCount(t *testing.T) { - os.Remove(__boltPath) - store, err := NewStore(__sqlitePath, __boltPath) - if err != nil { - t.Errorf("Failed to initialize: %v", err) - } - defer store.Close() - - stats, err := store.GetStats(nil) - clientCount := int(stats.ClientCount) - if 0 != clientCount { - t.Errorf("Failed to count correctly.") - } } func TestGetUpdatesAfter(t *testing.T) { diff --git a/src/hub/src/spreadspace.org/sfive/s5typesApi.go b/src/hub/src/spreadspace.org/sfive/s5typesApi.go index 515b869..525b6d3 100644 --- a/src/hub/src/spreadspace.org/sfive/s5typesApi.go +++ b/src/hub/src/spreadspace.org/sfive/s5typesApi.go @@ -55,19 +55,6 @@ type StatisticsDataContainer struct { Data []StatisticsData `json:"data"` } -type StatsFilter struct { - start *time.Time - end *time.Time - hostname *string - contentId *string - format *string - quality *string - tagsAny []string - afterUpdateId *int - limit *int - sortOrder *string -} - func (self *StatisticsData) CopyFromSourceId(id *SourceId) { self.Hostname = id.Hostname self.StreamId = id.StreamId diff --git a/src/hub/test-client b/src/hub/test-client index d5756e7..fcb9a98 100755 --- a/src/hub/test-client +++ b/src/hub/test-client @@ -10,16 +10,13 @@ echo pipe-gram: import sample-gram.json echo ---------------------------------- while read x; do echo "$x" | socat stdio "unix-sendto:$TEST_D/pipegram"; done < ../../dat/sample-gram.json +echo post update +echo ----------- +curl -i --data @../../dat/sample-post.json 'http://localhost:8000/updates' + echo show query result echo ----------------- -curl -i 'http://localhost:8000/updates?from=2013-10-21T00:00:00Z&to=2013-10-21T12:31:00Z' - -echo '\npost update' -echo ------------ -curl -i --data @../../dat/sample-post.json 'http://localhost:8000/updates' +curl -i 'http://localhost:8000/updates' -echo show stats -echo ---------- -curl -i 'http://localhost:8000/stats' echo '\n\ndone' -- cgit v1.2.3 From 2236a89df8a09f0b2b93ffc551fa144b683e0f86 Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Thu, 27 Apr 2017 01:35:03 +0200 Subject: hubinfo table moved to bolt as well --- src/hub/src/spreadspace.org/sfive/s5srv.go | 10 ++--- src/hub/src/spreadspace.org/sfive/s5srvForward.go | 7 +--- .../src/spreadspace.org/sfive/s5srvForwardEs.go | 7 +--- src/hub/src/spreadspace.org/sfive/s5srvWeb.go | 2 +- src/hub/src/spreadspace.org/sfive/s5store.go | 45 ++++++++++++---------- src/hub/src/spreadspace.org/sfive/s5typesStore.go | 10 ++--- 6 files changed, 35 insertions(+), 46 deletions(-) (limited to 'src') diff --git a/src/hub/src/spreadspace.org/sfive/s5srv.go b/src/hub/src/spreadspace.org/sfive/s5srv.go index a69adfe..6f5ebbb 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srv.go +++ b/src/hub/src/spreadspace.org/sfive/s5srv.go @@ -25,8 +25,7 @@ type getUpdatesToken struct { } type getHubIdResult struct { - id string - err error + id string } type getHubIdToken struct { @@ -88,8 +87,7 @@ func (self StatsSinkServer) appendActor() { values, err := self.store.GetUpdates() token.response <- getUpdatesResult{values, err} case token := <-self.getHubIdChan: - storeId, err := self.store.GetStoreId() - token.response <- getHubIdResult{storeId, err} + token.response <- getHubIdResult{self.store.GetStoreId()} case token := <-self.getLastUpdateIdChan: lastUpdateId, err := self.store.GetLastUpdateId() if lastUpdateId != nil { @@ -117,12 +115,12 @@ func (self StatsSinkServer) getUpdatesInvoke() ([]StatisticsData, error) { return res.values, res.err } -func (self StatsSinkServer) getHubIdInvoke() (string, error) { +func (self StatsSinkServer) getHubIdInvoke() string { token := getHubIdToken{response: make(chan getHubIdResult, 1)} defer close(token.response) self.getHubIdChan <- token res := <-token.response - return res.id, res.err + return res.id } func (self StatsSinkServer) getLastUpdateIdInvoke() (int, error) { diff --git a/src/hub/src/spreadspace.org/sfive/s5srvForward.go b/src/hub/src/spreadspace.org/sfive/s5srvForward.go index a072b2a..418d195 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvForward.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvForward.go @@ -21,12 +21,7 @@ func findMaxId(values []StatisticsData) int { } func (self StatsSinkServer) getLastUpdate(baseurl string, client *http.Client) (latestId int, storeId string, err error) { - storeId, err = self.getHubIdInvoke() - - if err != nil { - s5l.Printf("fwd: failed to get own hubid: %v\n", err) - return - } + storeId = self.getHubIdInvoke() var resp *http.Response resp, err = client.Get(baseurl + "/lastupdate/" + storeId) diff --git a/src/hub/src/spreadspace.org/sfive/s5srvForwardEs.go b/src/hub/src/spreadspace.org/sfive/s5srvForwardEs.go index 19abb1e..da5ff80 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvForwardEs.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvForwardEs.go @@ -18,12 +18,7 @@ const lastUpdateJson = `{ func (self StatsSinkServer) getLastUpdateEs(baseurl string, client *http.Client) (latestId int, storeId string, err error) { url := baseurl + "/dataupdate/_search?search_type=count" - storeId, err = self.getHubIdInvoke() - - if err != nil { - s5l.Printf("fwd-es: failed to get own hubid: %v\n", err) - return - } + storeId = self.getHubIdInvoke() queryJson := fmt.Sprintf(lastUpdateJson, storeId) s5tl.Printf("fwd-es: query: %s", queryJson) diff --git a/src/hub/src/spreadspace.org/sfive/s5srvWeb.go b/src/hub/src/spreadspace.org/sfive/s5srvWeb.go index 54daa65..6292ba8 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvWeb.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvWeb.go @@ -14,7 +14,7 @@ import ( func (self StatsSinkServer) healthz(c web.C, w http.ResponseWriter, r *http.Request) { // TODO: do a more sophisticated check - fmt.Fprintf(w, "OK\n") + fmt.Fprintf(w, "%s\n", self.store.GetStoreId()) } func (self StatsSinkServer) getSourcesList(c web.C, w http.ResponseWriter, r *http.Request) { diff --git a/src/hub/src/spreadspace.org/sfive/s5store.go b/src/hub/src/spreadspace.org/sfive/s5store.go index c4e1676..3ccc202 100644 --- a/src/hub/src/spreadspace.org/sfive/s5store.go +++ b/src/hub/src/spreadspace.org/sfive/s5store.go @@ -54,7 +54,7 @@ func updateFromStatisticsData(value StatisticsData) (dataUpdateDb, []ClientData, return du, cd, src } -func initDbBolt(boltPath string) (boltDb *bolt.DB, err error) { +func initDbBolt(boltPath string) (boltDb *bolt.DB, hubId string, err error) { boltDb, err = bolt.Open(boltPath, 0600, &bolt.Options{Timeout: 1 * time.Second}) if err != nil { return @@ -73,14 +73,30 @@ func initDbBolt(boltPath string) (boltDb *bolt.DB, err error) { if _, err := tx.CreateBucketIfNotExists([]byte(userAgentsFwdBn)); err != nil { return err } - _, err := tx.CreateBucketIfNotExists([]byte(userAgentsRevBn)) - return err + if _, err := tx.CreateBucketIfNotExists([]byte(userAgentsRevBn)); err != nil { + return err + } + + b, err := tx.CreateBucketIfNotExists([]byte(hubInfoBn)) + if err != nil { + return err + } + bHubId := b.Get([]byte(hubUUIDKey)) + if bHubId == nil { + hubId = uuid.New() + if err := b.Put([]byte(hubUUIDKey), []byte(hubId)); err != nil { + return err + } + } else { + hubId = string(bHubId) + } + return nil }) return } -func initDbSqlite(sqlitePath string) (dbmap *gorp.DbMap, hubId string, err error) { +func initDbSqlite(sqlitePath string) (dbmap *gorp.DbMap, err error) { var db *sql.DB db, err = sql.Open("sqlite3", sqlitePath) @@ -92,24 +108,12 @@ func initDbSqlite(sqlitePath string) (dbmap *gorp.DbMap, hubId string, err error // dbmap.TraceOn("[gorp]", log.New(os.Stdout, "myapp:", log.Lmicroseconds)) dbmap.AddTableWithName(dataUpdateDb{}, dataUpdatesTn).SetKeys(true, "Id") - dbmap.AddTableWithName(hubInfoDb{}, hubInfoTn).SetKeys(false, "Name") // TODO use some real migration, yadda yadda if err = dbmap.CreateTablesIfNotExists(); err != nil { return } - hubId, err = dbmap.SelectStr("select Value from " + hubInfoTn + " where Name = 'HubUuid'") - // TODO handle only not-found this way - if err != nil || hubId == "" { - hubId = uuid.New() - _, err = db.Exec("insert into "+hubInfoTn+" values ('HubUuid', ?)", hubId) - if err != nil { - hubId = "" - return - } - } - return } @@ -398,18 +402,17 @@ func (s sqliteStore) GetLastUpdateId() (updateId *int, err error) { return } -func (s sqliteStore) GetStoreId() (uuid string, err error) { - uuid, err = s.db.SelectStr("select Value from HubInfo where Name = ?", "HubUuid") - return +func (s sqliteStore) GetStoreId() string { + return s.hubId } func NewStore(sqlitePath, boltPath string) (sqliteStore, error) { - boltDb, err := initDbBolt(boltPath) + boltDb, hubid, err := initDbBolt(boltPath) if err != nil { return sqliteStore{}, err } - db, hubid, err := initDbSqlite(sqlitePath) + db, err := initDbSqlite(sqlitePath) if err != nil { boltDb.Close() return sqliteStore{}, err diff --git a/src/hub/src/spreadspace.org/sfive/s5typesStore.go b/src/hub/src/spreadspace.org/sfive/s5typesStore.go index a6ac3a9..fd21337 100644 --- a/src/hub/src/spreadspace.org/sfive/s5typesStore.go +++ b/src/hub/src/spreadspace.org/sfive/s5typesStore.go @@ -18,20 +18,18 @@ var ( const ( // sqlite table names dataUpdatesTn = "DataUpdates" - hubInfoTn = "HubInfo" // bolt bucket names + hubInfoBn = "HubInfo" sourcesFwdBn = "SourcesFwd" sourcesRevBn = "SourcesRev" clientDataBn = "ClientData" userAgentsFwdBn = "UserAgentsFwd" userAgentsRevBn = "UserAgentsRev" -) -type hubInfoDb struct { - Name string - Value string -} + // well-known keys + hubUUIDKey = "HubUUID" +) // stored in sourcesRevBn type streamIdDb struct { -- cgit v1.2.3 From 89e09629904d77a4b770315749e855d23af55bf6 Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Thu, 27 Apr 2017 02:51:19 +0200 Subject: completely remove sqlite --- doc/protocol.md | 1 + src/hub/Makefile | 4 +- src/hub/src/spreadspace.org/sfive-hub/s5hub.go | 2 +- src/hub/src/spreadspace.org/sfive/s5srv.go | 14 +- src/hub/src/spreadspace.org/sfive/s5srvForward.go | 4 +- src/hub/src/spreadspace.org/sfive/s5srvWeb.go | 4 +- src/hub/src/spreadspace.org/sfive/s5store.go | 255 ++++++++-------------- src/hub/src/spreadspace.org/sfive/s5store_test.go | 15 +- src/hub/src/spreadspace.org/sfive/s5typesApi.go | 4 +- src/hub/src/spreadspace.org/sfive/s5typesStore.go | 64 ++++-- src/hub/test-bolt | 3 +- src/hub/test-bolter | 3 +- src/hub/test-fwd | 3 +- src/hub/test-fwd-es | 3 +- src/hub/test-fwd-piwik | 3 +- src/hub/test-sqlite | 5 - src/hub/test-srv | 3 +- 17 files changed, 158 insertions(+), 232 deletions(-) delete mode 100755 src/hub/test-sqlite (limited to 'src') diff --git a/doc/protocol.md b/doc/protocol.md index 74bdbd2..fdd51ee 100644 --- a/doc/protocol.md +++ b/doc/protocol.md @@ -24,6 +24,7 @@ values from data updates override values from init. Stateless interfaces will no init messages and therefore all values must be defined here. If an interface (i.e. REST) has other means to detect protocol versions the version field may be omitted entirely. +The start-time will be processesd and stored with millisecond precision. { "version": 1, diff --git a/src/hub/Makefile b/src/hub/Makefile index 43b4d9d..1739757 100644 --- a/src/hub/Makefile +++ b/src/hub/Makefile @@ -38,9 +38,7 @@ endif EXECUTEABLE := sfive-hub -LIBS := "gopkg.in/gorp.v2" \ - "github.com/mattn/go-sqlite3" \ - "github.com/boltdb/bolt" \ +LIBS := "github.com/boltdb/bolt" \ "github.com/zenazn/goji" \ "github.com/pborman/uuid" \ "github.com/equinox0815/graphite-golang" diff --git a/src/hub/src/spreadspace.org/sfive-hub/s5hub.go b/src/hub/src/spreadspace.org/sfive-hub/s5hub.go index 7ff58b5..9589812 100644 --- a/src/hub/src/spreadspace.org/sfive-hub/s5hub.go +++ b/src/hub/src/spreadspace.org/sfive-hub/s5hub.go @@ -12,7 +12,7 @@ import ( var s5hl = log.New(os.Stderr, "[s5hub]\t", log.LstdFlags) func main() { - db := flag.String("db", "/var/lib/sfive/", "directory to store the database files") + db := flag.String("db", "/var/lib/sfive/db.bolt", "path to the database file") pipe := flag.String("pipe", "/var/run/sfive/pipe", "path to the unix pipe for the pipeserver") ppipe := flag.String("pipegram", "/var/run/sfive/pipegram", "path to the unix datagram pipe for the pipeserver") startPipe := flag.Bool("start-pipe-server", true, "start a connection oriented pipe server; see option pipe") diff --git a/src/hub/src/spreadspace.org/sfive/s5srv.go b/src/hub/src/spreadspace.org/sfive/s5srv.go index 6f5ebbb..c5f6e21 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srv.go +++ b/src/hub/src/spreadspace.org/sfive/s5srv.go @@ -1,7 +1,6 @@ package sfive import ( - "path/filepath" "time" ) @@ -42,7 +41,7 @@ type getLastUpdateIdToken struct { } type StatsSinkServer struct { - store sqliteStore + store Store quit chan bool done chan bool appendData chan StatisticsData @@ -90,11 +89,7 @@ func (self StatsSinkServer) appendActor() { token.response <- getHubIdResult{self.store.GetStoreId()} case token := <-self.getLastUpdateIdChan: lastUpdateId, err := self.store.GetLastUpdateId() - if lastUpdateId != nil { - token.response <- getLastUpdateIdResult{*lastUpdateId, err} - } else { - token.response <- getLastUpdateIdResult{0, err} - } + token.response <- getLastUpdateIdResult{lastUpdateId, err} } } } @@ -147,11 +142,8 @@ func (self StatsSinkServer) Close() { func NewServer(dbPath string) (server *StatsSinkServer, err error) { // TODO read configuration and create instance with correct settings - sqlitePath := filepath.Join(dbPath, "db.sqlite") - boltPath := filepath.Join(dbPath, "db.bolt") - server = new(StatsSinkServer) - server.store, err = NewStore(sqlitePath, boltPath) + server.store, err = NewStore(dbPath) if err != nil { return } diff --git a/src/hub/src/spreadspace.org/sfive/s5srvForward.go b/src/hub/src/spreadspace.org/sfive/s5srvForward.go index 418d195..bdb0cbf 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvForward.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvForward.go @@ -13,8 +13,8 @@ func findMaxId(values []StatisticsData) int { maxId := -1 for i := range values { id := values[i].SourceHubDataUpdateId - if id != nil && *id > maxId { - maxId = *id + if id > maxId { + maxId = id } } return maxId diff --git a/src/hub/src/spreadspace.org/sfive/s5srvWeb.go b/src/hub/src/spreadspace.org/sfive/s5srvWeb.go index 6292ba8..a474f47 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvWeb.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvWeb.go @@ -137,9 +137,7 @@ func (self StatsSinkServer) getLastUpdateIdForUuid(c web.C, w http.ResponseWrite http.Error(w, fmt.Sprintf("failed to retrieve %s: %v", resourceName, err), http.StatusInternalServerError) return } - if value != nil { - fmt.Fprintf(w, "%d", *value) - } + fmt.Fprintf(w, "%d", value) } func (self StatsSinkServer) ServeWeb(vizAppLocation string) { diff --git a/src/hub/src/spreadspace.org/sfive/s5store.go b/src/hub/src/spreadspace.org/sfive/s5store.go index 3ccc202..1339a4b 100644 --- a/src/hub/src/spreadspace.org/sfive/s5store.go +++ b/src/hub/src/spreadspace.org/sfive/s5store.go @@ -1,66 +1,28 @@ package sfive import ( - "database/sql" "encoding/json" "time" - // needed for gorp tracing - // "log" - // "os" - "github.com/boltdb/bolt" - _ "github.com/mattn/go-sqlite3" "github.com/pborman/uuid" - "gopkg.in/gorp.v2" ) -type sqliteStore struct { - db *gorp.DbMap - dbBolt *bolt.DB - hubId string -} - -func sourceFromStatisticsData(value StatisticsData) sourceDb { - return sourceDb{ - Hostname: value.SourceId.Hostname, - StreamId: streamIdDb{ - ContentId: value.SourceId.StreamId.ContentId, - Format: value.SourceId.StreamId.Format, - Quality: value.SourceId.StreamId.Quality, - }, - Tags: value.SourceId.Tags, - } -} - -func dataUpdateFromStatisticsData(value StatisticsData) dataUpdateDb { - return dataUpdateDb{ - -1, - -1, - value.SourceHubUuid, - value.SourceHubDataUpdateId, - value.StartTime.Unix(), - value.Duration, - value.Data.ClientCount, - value.Data.BytesReceived, - value.Data.BytesSent} -} - -func updateFromStatisticsData(value StatisticsData) (dataUpdateDb, []ClientData, sourceDb) { - du := dataUpdateFromStatisticsData(value) - cd := value.Data.Clients - src := sourceFromStatisticsData(value) - - return du, cd, src +type Store struct { + hubId string + db *bolt.DB } -func initDbBolt(boltPath string) (boltDb *bolt.DB, hubId string, err error) { +func initDb(boltPath string) (boltDb *bolt.DB, hubId string, err error) { boltDb, err = bolt.Open(boltPath, 0600, &bolt.Options{Timeout: 1 * time.Second}) if err != nil { return } err = boltDb.Update(func(tx *bolt.Tx) error { + if _, err := tx.CreateBucketIfNotExists([]byte(dataUpdatesBn)); err != nil { + return err + } if _, err := tx.CreateBucketIfNotExists([]byte(sourcesFwdBn)); err != nil { return err } @@ -96,37 +58,14 @@ func initDbBolt(boltPath string) (boltDb *bolt.DB, hubId string, err error) { return } -func initDbSqlite(sqlitePath string) (dbmap *gorp.DbMap, err error) { - - var db *sql.DB - db, err = sql.Open("sqlite3", sqlitePath) - if err != nil { - return - } - - dbmap = &gorp.DbMap{Db: db, Dialect: gorp.SqliteDialect{}} - // dbmap.TraceOn("[gorp]", log.New(os.Stdout, "myapp:", log.Lmicroseconds)) - - dbmap.AddTableWithName(dataUpdateDb{}, dataUpdatesTn).SetKeys(true, "Id") - - // TODO use some real migration, yadda yadda - if err = dbmap.CreateTablesIfNotExists(); err != nil { - return - } - - return -} - -func (s sqliteStore) insertDataUpdateEntry(srcId int, du *dataUpdateDb) (err error) { - du.SourceId = srcId - err = s.db.Insert(du) - if err != nil { - return - } - return +func updateFromStatisticsData(value StatisticsData) (dataUpdateDb, []ClientData, sourceDb) { + du := NewDataUpdateDb(value) + cd := value.Data.Clients + src := NewSourceDb(value) + return du, cd, src } -func (s sqliteStore) insertNewSource(tx *bolt.Tx, src sourceDb) (srcId int, err error) { +func (s Store) insertNewSource(tx *bolt.Tx, src sourceDb) (srcId int, err error) { bf := tx.Bucket([]byte(sourcesFwdBn)) bf.FillPercent = 1.0 // we only do appends br := tx.Bucket([]byte(sourcesRevBn)) @@ -155,7 +94,12 @@ func (s sqliteStore) insertNewSource(tx *bolt.Tx, src sourceDb) (srcId int, err return srcId, err } -func (s sqliteStore) insertNewUserAgent(tx *bolt.Tx, ua string) (uaId int, err error) { +func (s Store) insertDataUpdateEntry(tx *bolt.Tx, srcId int, du *dataUpdateDb) (duId int, err error) { + // TODO: add me + return +} + +func (s Store) insertNewUserAgent(tx *bolt.Tx, ua string) (uaId int, err error) { bf := tx.Bucket([]byte(userAgentsFwdBn)) bf.FillPercent = 1.0 // we only do appends br := tx.Bucket([]byte(userAgentsRevBn)) @@ -178,7 +122,7 @@ func (s sqliteStore) insertNewUserAgent(tx *bolt.Tx, ua string) (uaId int, err e return uaId, err } -func (s sqliteStore) insertDataUpdateClientEntries(tx *bolt.Tx, duId int, cd []ClientData) error { +func (s Store) insertDataUpdateClientEntries(tx *bolt.Tx, duId int, cd []ClientData) error { if len(cd) == 0 { return nil } @@ -202,35 +146,26 @@ func (s sqliteStore) insertDataUpdateClientEntries(tx *bolt.Tx, duId int, cd []C return b.Put(itob(duId), jsonData) } -func (s sqliteStore) appendItem(tx *bolt.Tx, du dataUpdateDb, cd []ClientData, src sourceDb) (err error) { +func (s Store) appendItem(tx *bolt.Tx, du dataUpdateDb, cd []ClientData, src sourceDb) (err error) { var srcId int if srcId, err = s.insertNewSource(tx, src); err != nil { return } - if err = s.insertDataUpdateEntry(srcId, &du); err != nil { + var duId int + if duId, err = s.insertDataUpdateEntry(tx, srcId, &du); err != nil { return } - if err = s.insertDataUpdateClientEntries(tx, du.Id, cd); err != nil { + if err = s.insertDataUpdateClientEntries(tx, duId, cd); err != nil { return } return } -func (s sqliteStore) Append(update StatisticsData) (err error) { - return s.AppendMany([]StatisticsData{update}) -} - -func (s sqliteStore) AppendMany(updates []StatisticsData) (err error) { - var tx *gorp.Transaction - tx, err = s.db.Begin() - if err != nil { - return - } - - err = s.dbBolt.Update(func(tx *bolt.Tx) error { +func (s Store) AppendMany(updates []StatisticsData) (err error) { + return s.db.Update(func(tx *bolt.Tx) error { for _, update := range updates { du, cd, src := updateFromStatisticsData(update) if err := s.appendItem(tx, du, cd, src); err != nil { @@ -239,16 +174,13 @@ func (s sqliteStore) AppendMany(updates []StatisticsData) (err error) { } return nil }) +} - if err != nil { - tx.Rollback() - return - } - - return tx.Commit() +func (s Store) Append(update StatisticsData) (err error) { + return s.AppendMany([]StatisticsData{update}) } -func (s sqliteStore) getSource(tx *bolt.Tx, id int) (res sourceDb, err error) { +func (s Store) getSource(tx *bolt.Tx, id int) (res sourceDb, err error) { b := tx.Bucket([]byte(sourcesRevBn)) jsonData := b.Get(itob(id)) @@ -262,9 +194,9 @@ func (s sqliteStore) getSource(tx *bolt.Tx, id int) (res sourceDb, err error) { return } -func (s sqliteStore) GetSources() (res []SourceId, err error) { +func (s Store) GetSources() (res []SourceId, err error) { res = []SourceId{} - err = s.dbBolt.View(func(tx *bolt.Tx) error { + err = s.db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(sourcesRevBn)) c := b.Cursor() for k, v := c.First(); k != nil; k, v = c.Next() { @@ -281,8 +213,8 @@ func (s sqliteStore) GetSources() (res []SourceId, err error) { return } -func (s sqliteStore) GetSource(id int) (res SourceId, err error) { - err = s.dbBolt.View(func(tx *bolt.Tx) error { +func (s Store) GetSource(id int) (res SourceId, err error) { + err = s.db.View(func(tx *bolt.Tx) error { src, err := s.getSource(tx, id) if err != nil { return err @@ -293,12 +225,12 @@ func (s sqliteStore) GetSource(id int) (res SourceId, err error) { return } -func (s sqliteStore) GetUpdate(id int) (res dataUpdateDb, err error) { - err = s.db.SelectOne(&res, "select * from "+dataUpdatesTn+" where Id = ?", id) +func (s Store) GetUpdate(id int) (res dataUpdateDb, err error) { + // TODO: implement me return } -func (s sqliteStore) getClientsByUpdateId(tx *bolt.Tx, id int) (res []ClientData, err error) { +func (s Store) getClientsByUpdateId(tx *bolt.Tx, id int) (res []ClientData, err error) { bc := tx.Bucket([]byte(clientDataBn)) bu := tx.Bucket([]byte(userAgentsRevBn)) @@ -321,9 +253,9 @@ func (s sqliteStore) getClientsByUpdateId(tx *bolt.Tx, id int) (res []ClientData return } -func (s sqliteStore) CreateStatisticsDataFrom(tx *bolt.Tx, dat dataUpdateDb) (res StatisticsData, err error) { +func (s Store) CreateStatisticsDataFrom(tx *bolt.Tx, duId int, dat dataUpdateDb) (res StatisticsData, err error) { var clients []ClientData - if clients, err = s.getClientsByUpdateId(tx, dat.Id); err != nil { + if clients, err = s.getClientsByUpdateId(tx, duId); err != nil { return } var src sourceDb @@ -331,7 +263,7 @@ func (s sqliteStore) CreateStatisticsDataFrom(tx *bolt.Tx, dat dataUpdateDb) (re return } - res.CopyFromDataUpdateDb(dat, s.hubId) + res.CopyFromDataUpdateDb(dat, duId, s.hubId) res.Hostname = src.Hostname res.StreamId.ContentId = src.StreamId.ContentId res.StreamId.Format = src.StreamId.Format @@ -341,35 +273,24 @@ func (s sqliteStore) CreateStatisticsDataFrom(tx *bolt.Tx, dat dataUpdateDb) (re return } -func (s sqliteStore) CreateStatisticsDatasFrom(dat []interface{}) (res []StatisticsData, err error) { - err = s.dbBolt.View(func(tx *bolt.Tx) error { - for i := range dat { - sd, err := s.CreateStatisticsDataFrom(tx, *dat[i].(*dataUpdateDb)) - if err != nil { - return err - } - res = append(res, sd) - } - return nil - }) - return -} - -func (s sqliteStore) GetUpdatesAfter(id int) (res []StatisticsData, err error) { - parameters := make(map[string]interface{}) - sql := "SELECT * FROM " + dataUpdatesTn + " WHERE Id > :afterUpdateId limit :limit" - parameters["afterUpdateId"] = id - parameters["limit"] = 5000 // TODO: hardcoded value - var updates []interface{} - updates, err = s.db.Select(dataUpdateDb{}, sql, parameters) - s5tl.Printf("sql: %s", sql) - if err == nil { - res, _ = s.CreateStatisticsDatasFrom(updates) - } +func (s Store) GetUpdatesAfter(id int) (res []StatisticsData, err error) { + // err = s.db.View(func(tx *bolt.Tx) error { + // // TODO: iterate over ids + // duId := 1 + + // for i := range dat { + // sd, err := s.CreateStatisticsDataFrom(tx, duId, du) + // if err != nil { + // return err + // } + // res = append(res, sd) + // } + // return nil + // }) return } -func (s sqliteStore) GetUpdates() (res []StatisticsData, err error) { +func (s Store) GetUpdates() (res []StatisticsData, err error) { return s.GetUpdatesAfter(-1) } @@ -377,50 +298,50 @@ type lastUpdateQueryResult struct { MaxDataUpdateId *int } -func (s sqliteStore) GetLastUpdateForUuid(uuid string) (updateId *int, err error) { - result := lastUpdateQueryResult{} - err = s.db.SelectOne( - &result, - "select max(SourceHubDataUpdateId) as MaxDataUpdateId from "+dataUpdatesTn+" where SourceHubUuid = ?", - uuid) - if err == nil { - updateId = result.MaxDataUpdateId - } else { - s5l.Printf("db: failed to find max SourceHubDataUpdateId for %s: %v", uuid, err) - } +func (s Store) GetLastUpdateForUuid(uuid string) (updateId int, err error) { + // TODO: implement me! + updateId = -1 + + // result := lastUpdateQueryResult{} + // err = s.db.SelectOne( + // &result, + // "select max(SourceHubDataUpdateId) as MaxDataUpdateId from "+dataUpdatesTn+" where SourceHubUuid = ?", + // uuid) + // if err == nil { + // updateId = result.MaxDataUpdateId + // } else { + // s5l.Printf("db: failed to find max SourceHubDataUpdateId for %s: %v", uuid, err) + // } + // return return } -func (s sqliteStore) GetLastUpdateId() (updateId *int, err error) { - result := lastUpdateQueryResult{} - err = s.db.SelectOne(&result, "select max(Id) as MaxDataUpdateId from "+dataUpdatesTn) - if err == nil { - updateId = result.MaxDataUpdateId - } else { - s5l.Printf("db: failed to find max DataUpdateId: %v", err) - } +func (s Store) GetLastUpdateId() (updateId int, err error) { + // TODO: implement me! + updateId = -1 + + // result := lastUpdateQueryResult{} + // err = s.db.SelectOne(&result, "select max(Id) as MaxDataUpdateId from "+dataUpdatesTn) + // if err == nil { + // updateId = result.MaxDataUpdateId + // } else { + // s5l.Printf("db: failed to find max DataUpdateId: %v", err) + // } return } -func (s sqliteStore) GetStoreId() string { +func (s Store) GetStoreId() string { return s.hubId } -func NewStore(sqlitePath, boltPath string) (sqliteStore, error) { - boltDb, hubid, err := initDbBolt(boltPath) - if err != nil { - return sqliteStore{}, err - } - - db, err := initDbSqlite(sqlitePath) +func NewStore(dbPath string) (Store, error) { + db, hubid, err := initDb(dbPath) if err != nil { - boltDb.Close() - return sqliteStore{}, err + return Store{}, err } - return sqliteStore{db, boltDb, hubid}, nil + return Store{hubid, db}, nil } -func (s sqliteStore) Close() { - s.db.Db.Close() - s.dbBolt.Close() +func (s Store) Close() { + s.db.Close() } diff --git a/src/hub/src/spreadspace.org/sfive/s5store_test.go b/src/hub/src/spreadspace.org/sfive/s5store_test.go index fe43bf5..38e7bf7 100644 --- a/src/hub/src/spreadspace.org/sfive/s5store_test.go +++ b/src/hub/src/spreadspace.org/sfive/s5store_test.go @@ -9,8 +9,7 @@ import ( ) var ( - __sqlitePath = "file:memdb1?mode=memory&cache=shared" - __boltPath = "/run/s5hub_testing_db.bolt" + __boltPath = "/run/s5hub_testing_db.bolt" ) func TestMain(m *testing.M) { @@ -24,7 +23,7 @@ func TestMain(m *testing.M) { func TestAppend(t *testing.T) { os.Remove(__boltPath) - store, err := NewStore(__sqlitePath, __boltPath) + store, err := NewStore(__boltPath) if err != nil { t.Errorf("Failed to initialize: %v", err) return @@ -35,7 +34,7 @@ func TestAppend(t *testing.T) { update := DataUpdate{Data: SourceData{BytesSent: 1, ClientCount: 3, BytesReceived: 1}, StartTime: startTime, Duration: 5000} streamId := StreamId{ContentId: "content", Format: "7bitascii", Quality: QualityHigh} source := SourceId{Hostname: "localhost", Tags: []string{"tag1", "master"}, StreamId: streamId, Version: 1} - dat := StatisticsData{nil, nil, source, update} + dat := StatisticsData{"", -1, source, update} err = store.Append(dat) if err != nil { @@ -46,7 +45,7 @@ func TestAppend(t *testing.T) { func TestGetUpdatesAfter(t *testing.T) { os.Remove(__boltPath) - store, err := NewStore(__sqlitePath, __boltPath) + store, err := NewStore(__boltPath) if err != nil { t.Errorf("Failed to initialize: %v", err) return @@ -57,7 +56,7 @@ func TestGetUpdatesAfter(t *testing.T) { update := DataUpdate{Data: SourceData{BytesSent: 1, ClientCount: 3, BytesReceived: 1}, StartTime: startTime, Duration: 5000} streamId := StreamId{ContentId: "content", Format: "7bitascii", Quality: QualityHigh} source := SourceId{Hostname: "localhost", Tags: []string{"tag1", "master"}, StreamId: streamId, Version: 1} - dat := StatisticsData{nil, nil, source, update} + dat := StatisticsData{"", -1, source, update} err = store.Append(dat) if err != nil { @@ -122,7 +121,7 @@ func generateStatisticsData(n int) (data []StatisticsData) { func BenchmarkAppendMany(b *testing.B) { os.Remove(__boltPath) - store, err := NewStore(__sqlitePath, __boltPath) + store, err := NewStore(__boltPath) if err != nil { b.Errorf("Failed to initialize: %v", err) } @@ -138,7 +137,7 @@ func BenchmarkAppendMany(b *testing.B) { func BenchmarkGetUpdatesAfter(b *testing.B) { os.Remove(__boltPath) - store, err := NewStore(__sqlitePath, __boltPath) + store, err := NewStore(__boltPath) if err != nil { b.Errorf("Failed to initialize: %v", err) } diff --git a/src/hub/src/spreadspace.org/sfive/s5typesApi.go b/src/hub/src/spreadspace.org/sfive/s5typesApi.go index 525b6d3..ad6deaf 100644 --- a/src/hub/src/spreadspace.org/sfive/s5typesApi.go +++ b/src/hub/src/spreadspace.org/sfive/s5typesApi.go @@ -41,8 +41,8 @@ type DataUpdate struct { } type StatisticsData struct { - SourceHubUuid *string - SourceHubDataUpdateId *int + SourceHubUuid string `json:"SourceHubUuid,omitempty"` + SourceHubDataUpdateId int `json:"SourceHubDataUpdateId,omitempty"` SourceId DataUpdate } diff --git a/src/hub/src/spreadspace.org/sfive/s5typesStore.go b/src/hub/src/spreadspace.org/sfive/s5typesStore.go index fd21337..d6de68e 100644 --- a/src/hub/src/spreadspace.org/sfive/s5typesStore.go +++ b/src/hub/src/spreadspace.org/sfive/s5typesStore.go @@ -12,15 +12,10 @@ var ( ErrNotFound = errors.New("not found") ) -// compared to JSON DTOs, DB types are flattened, and use key-relations instead of collections -// this is very much not normalized at all, because I'm too lazy to type - const ( - // sqlite table names - dataUpdatesTn = "DataUpdates" - - // bolt bucket names + // bucket names hubInfoBn = "HubInfo" + dataUpdatesBn = "DataUpdates" sourcesFwdBn = "SourcesFwd" sourcesRevBn = "SourcesRev" clientDataBn = "ClientData" @@ -44,6 +39,18 @@ type sourceDb struct { Tags []string `json:"t"` } +func NewSourceDb(value StatisticsData) sourceDb { + return sourceDb{ + Hostname: value.SourceId.Hostname, + StreamId: streamIdDb{ + ContentId: value.SourceId.StreamId.ContentId, + Format: value.SourceId.StreamId.Format, + Quality: value.SourceId.StreamId.Quality, + }, + Tags: value.SourceId.Tags, + } +} + func (s sourceDb) String() string { return fmt.Sprintf("%s/%s/%s/%s/%s", s.Hostname, s.StreamId.ContentId, s.StreamId.Format, s.StreamId.Quality, strings.Join(s.Tags, ",")) } @@ -66,30 +73,39 @@ type clientDataDb struct { // stored in dataUpdatesTn // in DB, StatisticsData/DataUpdate is flattened compared to JSON DTOs type dataUpdateDb struct { - Id int - SourceId int // foreign key to sourcesTn - SourceHubUuid *string - SourceHubDataUpdateId *int - StartTime int64 // time.Time - Duration int64 // duration in milliseconds - ClientCount uint - BytesReceived uint - BytesSent uint + SourceHubUuid string `json:"h,omitempty"` + SourceHubDataUpdateId int `json:"hi,omitempty"` + SourceId int `json:"si"` + StartTime int64 `json:"st"` // unix timestamp in milliseconds + Duration int64 `json:"du"` // duration in milliseconds + ClientCount uint `json:"cc"` + BytesReceived uint `json:"br"` + BytesSent uint `json:"bs"` } -func (s *StatisticsData) CopyFromDataUpdateDb(v dataUpdateDb, hubId string) { - if v.SourceHubUuid == nil { - s.SourceHubUuid = &hubId - } else { - s.SourceHubUuid = v.SourceHubUuid +func NewDataUpdateDb(v StatisticsData) dataUpdateDb { + return dataUpdateDb{ + v.SourceHubUuid, + v.SourceHubDataUpdateId, + -1, + int64(v.StartTime.Unix()*1000) + int64(v.StartTime.Nanosecond()/1000000), + v.Duration, + v.Data.ClientCount, + v.Data.BytesReceived, + v.Data.BytesSent, } - if v.SourceHubDataUpdateId == nil { - s.SourceHubDataUpdateId = &v.Id +} + +func (s *StatisticsData) CopyFromDataUpdateDb(v dataUpdateDb, vId int, hubId string) { + if v.SourceHubUuid == "" { + s.SourceHubUuid = hubId + s.SourceHubDataUpdateId = vId } else { + s.SourceHubUuid = v.SourceHubUuid s.SourceHubDataUpdateId = v.SourceHubDataUpdateId } - s.StartTime = time.Unix(v.StartTime, 0) + s.StartTime = time.Unix((v.StartTime / 1000), (v.StartTime%1000)*1000000) s.Duration = v.Duration s.Data.ClientCount = v.ClientCount s.Data.BytesReceived = v.BytesReceived diff --git a/src/hub/test-bolt b/src/hub/test-bolt index 7ac3dd2..9a26b04 100755 --- a/src/hub/test-bolt +++ b/src/hub/test-bolt @@ -1,6 +1,7 @@ #!/bin/sh TEST_D="./test" +TEST_DB="$TEST_D/db.bolt" BIN="$(go env GOPATH)/bin/bolt" if [ ! -x "$BIN" ]; then @@ -11,4 +12,4 @@ if [ ! -x "$BIN" ]; then exit 1 fi -exec "$(go env GOPATH)/bin/bolt" $@ "$TEST_D/db.bolt" +exec "$(go env GOPATH)/bin/bolt" $@ "$TEST_DB" diff --git a/src/hub/test-bolter b/src/hub/test-bolter index dc2e98d..9093d51 100755 --- a/src/hub/test-bolter +++ b/src/hub/test-bolter @@ -1,6 +1,7 @@ #!/bin/sh TEST_D="./test" +TEST_DB="$TEST_D/db.bolt" BIN="$(go env GOPATH)/bin/bolter" if [ ! -x "$BIN" ]; then @@ -11,4 +12,4 @@ if [ ! -x "$BIN" ]; then exit 1 fi -exec $BIN --file "$TEST_D/db.bolt" $@ +exec $BIN --file "$TEST_DB" $@ diff --git a/src/hub/test-fwd b/src/hub/test-fwd index eff7605..ce6e0e3 100755 --- a/src/hub/test-fwd +++ b/src/hub/test-fwd @@ -1,5 +1,6 @@ #!/bin/sh TEST_D="./test" +TEST_DB="$TEST_D/db.bolt" -exec ./bin/sfive-hub -db "$TEST_D" -start-pipe-server=false -start-pipegram-server=false -start-web-server=false -forward-url="http://localhost:8000" +exec ./bin/sfive-hub -db "$TEST_DB" -start-pipe-server=false -start-pipegram-server=false -start-web-server=false -forward-url="http://localhost:8000" diff --git a/src/hub/test-fwd-es b/src/hub/test-fwd-es index dc7979f..489fcdd 100755 --- a/src/hub/test-fwd-es +++ b/src/hub/test-fwd-es @@ -1,5 +1,6 @@ #!/bin/sh TEST_D="./test" +TEST_DB="$TEST_D/db.bolt" -exec ./bin/sfive-hub -db "$TEST_D" -start-pipe-server=false -start-pipegram-server=false -start-web-server=false -forward-es-url="http://stream.elevate.at:9200/e14" +exec ./bin/sfive-hub -db "$TEST_DB" -start-pipe-server=false -start-pipegram-server=false -start-web-server=false -forward-es-url="http://stream.elevate.at:9200/testing" diff --git a/src/hub/test-fwd-piwik b/src/hub/test-fwd-piwik index b6ac640..6143c70 100755 --- a/src/hub/test-fwd-piwik +++ b/src/hub/test-fwd-piwik @@ -1,5 +1,6 @@ #!/bin/sh TEST_D="./test" +TEST_DB="$TEST_D/db.bolt" -exec ./bin/sfive-hub -db "$TEST_D" -start-pipe-server=false -start-pipegram-server=false -start-web-server=false -forward-piwik-url="http://localhost/piwik.php" -piwik-token "asdfjlkasjdflk" -piwik-site-id 4 -piwik-site-url "https://stream.elevate.at" +exec ./bin/sfive-hub -db "$TEST_DB" -start-pipe-server=false -start-pipegram-server=false -start-web-server=false -forward-piwik-url="http://localhost/piwik.php" -piwik-token "asdfjlkasjdflk" -piwik-site-id 4 -piwik-site-url "https://stream.elevate.at" diff --git a/src/hub/test-sqlite b/src/hub/test-sqlite deleted file mode 100755 index 1c2dcb1..0000000 --- a/src/hub/test-sqlite +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -TEST_D="./test" - -exec sqlite3 "$TEST_D/db.sqlite" diff --git a/src/hub/test-srv b/src/hub/test-srv index 37d5897..803ac37 100755 --- a/src/hub/test-srv +++ b/src/hub/test-srv @@ -1,7 +1,8 @@ #!/bin/sh TEST_D="./test" +TEST_DB="$TEST_D/db.bolt" mkdir -p "$TEST_D" rm -f "$TEST_D/pipe" "$TEST_D/pipegram" -exec ./bin/sfive-hub -db "$TEST_D" -start-pipe-server -pipe "$TEST_D/pipe" -start-pipegram-server -pipegram "$TEST_D/pipegram" -start-web-server -viz-dir "$(pwd)/../viz" -bind=":8000" +exec ./bin/sfive-hub -db "$TEST_DB" -start-pipe-server -pipe "$TEST_D/pipe" -start-pipegram-server -pipegram "$TEST_D/pipegram" -start-web-server -viz-dir "$(pwd)/../viz" -bind=":8000" -- cgit v1.2.3 From 1ad822207078ed861c79297f454868b092cb0d62 Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Thu, 27 Apr 2017 03:01:00 +0200 Subject: no more stats in names --- src/hub/src/spreadspace.org/sfive/s5cvt.go | 20 ++++++++-------- src/hub/src/spreadspace.org/sfive/s5cvt_test.go | 6 ++--- src/hub/src/spreadspace.org/sfive/s5srv.go | 28 +++++++++++----------- src/hub/src/spreadspace.org/sfive/s5srvForward.go | 10 ++++---- .../src/spreadspace.org/sfive/s5srvForwardEs.go | 6 ++--- .../spreadspace.org/sfive/s5srvForwardGraphite.go | 6 ++--- .../src/spreadspace.org/sfive/s5srvForwardPiwik.go | 6 ++--- src/hub/src/spreadspace.org/sfive/s5srvPipe.go | 8 +++---- src/hub/src/spreadspace.org/sfive/s5srvWeb.go | 18 +++++++------- src/hub/src/spreadspace.org/sfive/s5store.go | 18 +++++++------- src/hub/src/spreadspace.org/sfive/s5store_test.go | 12 +++++----- src/hub/src/spreadspace.org/sfive/s5typesApi.go | 10 ++++---- src/hub/src/spreadspace.org/sfive/s5typesStore.go | 9 ++++--- 13 files changed, 78 insertions(+), 79 deletions(-) (limited to 'src') diff --git a/src/hub/src/spreadspace.org/sfive/s5cvt.go b/src/hub/src/spreadspace.org/sfive/s5cvt.go index a8ea4f8..12c0562 100644 --- a/src/hub/src/spreadspace.org/sfive/s5cvt.go +++ b/src/hub/src/spreadspace.org/sfive/s5cvt.go @@ -5,12 +5,12 @@ import ( "fmt" ) -type StatsDecoder interface { - Decode(jsonString []byte) (StatisticsData, error) +type FullDecoder interface { + Decode(jsonString []byte) (DataUpdateFull, error) } -type StatsEncoder interface { - Encode(data StatisticsData) []byte +type FullEncoder interface { + Encode(data DataUpdateFull) []byte } type StatefulDecoder struct { @@ -21,7 +21,7 @@ type PlainDecoder struct{} type PlainEncoder struct{} -func NewStatefulDecoder(jsonString []byte) (decoder StatsDecoder, err error) { +func NewStatefulDecoder(jsonString []byte) (decoder FullDecoder, err error) { res := new(StatefulDecoder) err = json.Unmarshal(jsonString, &res.sourceId) if err != nil { @@ -34,11 +34,11 @@ func NewStatefulDecoder(jsonString []byte) (decoder StatsDecoder, err error) { return } -func NewPlainDecoder() StatsDecoder { +func NewPlainDecoder() FullDecoder { return new(PlainDecoder) } -func (self *StatefulDecoder) Decode(jsonString []byte) (dat StatisticsData, err error) { +func (self *StatefulDecoder) Decode(jsonString []byte) (dat DataUpdateFull, err error) { dat.CopyFromSourceId(&self.sourceId) err = json.Unmarshal(jsonString, &dat) // like in PlainDecoder, let the client decide how to use partial results @@ -46,15 +46,15 @@ func (self *StatefulDecoder) Decode(jsonString []byte) (dat StatisticsData, err return } -func (self *PlainDecoder) Decode(jsonString []byte) (dat StatisticsData, err error) { +func (self *PlainDecoder) Decode(jsonString []byte) (dat DataUpdateFull, err error) { err = json.Unmarshal(jsonString, &dat) return } -func (self *PlainEncoder) Encode(data *StatisticsData) []byte { +func (self *PlainEncoder) Encode(data *DataUpdateFull) []byte { res, err := json.Marshal(data) if err != nil { - s5l.Panicln("failed to encode StatisticsData") + s5l.Panicln("failed to encode DataUpdateFull") } return res } diff --git a/src/hub/src/spreadspace.org/sfive/s5cvt_test.go b/src/hub/src/spreadspace.org/sfive/s5cvt_test.go index 32f35dd..30d45cf 100644 --- a/src/hub/src/spreadspace.org/sfive/s5cvt_test.go +++ b/src/hub/src/spreadspace.org/sfive/s5cvt_test.go @@ -16,8 +16,8 @@ var ( testData = "{" + sourceIdFields + "," + updateFields + "}" ) -func GetExpected() *StatisticsData { - expected := new(StatisticsData) +func GetExpected() *DataUpdateFull { + expected := new(DataUpdateFull) expected.CopyFromSourceId(&sourceIdDataStruct) expected.CopyFromUpdate(&updateDataStruct) return expected @@ -55,7 +55,7 @@ func TestDecodePlain(t *testing.T) { func TestEncode(t *testing.T) { ec := new(PlainEncoder) - td := new(StatisticsData) + td := new(DataUpdateFull) td.CopyFromSourceId(&sourceIdDataStruct) td.CopyFromUpdate(&updateDataStruct) t.Logf("dada: %v", ec.Encode(td)) diff --git a/src/hub/src/spreadspace.org/sfive/s5srv.go b/src/hub/src/spreadspace.org/sfive/s5srv.go index c5f6e21..047a318 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srv.go +++ b/src/hub/src/spreadspace.org/sfive/s5srv.go @@ -5,12 +5,12 @@ import ( ) type appendManyToken struct { - data []StatisticsData + data []DataUpdateFull response chan bool } type getUpdatesResult struct { - values []StatisticsData + values []DataUpdateFull err error } @@ -40,19 +40,19 @@ type getLastUpdateIdToken struct { response chan getLastUpdateIdResult } -type StatsSinkServer struct { +type Server struct { store Store quit chan bool done chan bool - appendData chan StatisticsData - appendManyData chan appendManyToken // chan []StatisticsData + appendData chan DataUpdateFull + appendManyData chan appendManyToken // chan []DataUpdateFull getUpdatesAfterChan chan getUpdatesAfterToken getUpdatesChan chan getUpdatesToken getHubIdChan chan getHubIdToken getLastUpdateIdChan chan getLastUpdateIdToken } -func (self StatsSinkServer) appendActor() { +func (self Server) appendActor() { defer func() { self.done <- true }() for { select { @@ -94,7 +94,7 @@ func (self StatsSinkServer) appendActor() { } } -func (self StatsSinkServer) getUpdatesAfterInvoke(id int) ([]StatisticsData, error) { +func (self Server) getUpdatesAfterInvoke(id int) ([]DataUpdateFull, error) { token := getUpdatesAfterToken{id: id, response: make(chan getUpdatesResult, 1)} defer close(token.response) self.getUpdatesAfterChan <- token @@ -102,7 +102,7 @@ func (self StatsSinkServer) getUpdatesAfterInvoke(id int) ([]StatisticsData, err return res.values, res.err } -func (self StatsSinkServer) getUpdatesInvoke() ([]StatisticsData, error) { +func (self Server) getUpdatesInvoke() ([]DataUpdateFull, error) { token := getUpdatesToken{response: make(chan getUpdatesResult, 1)} defer close(token.response) self.getUpdatesChan <- token @@ -110,7 +110,7 @@ func (self StatsSinkServer) getUpdatesInvoke() ([]StatisticsData, error) { return res.values, res.err } -func (self StatsSinkServer) getHubIdInvoke() string { +func (self Server) getHubIdInvoke() string { token := getHubIdToken{response: make(chan getHubIdResult, 1)} defer close(token.response) self.getHubIdChan <- token @@ -118,7 +118,7 @@ func (self StatsSinkServer) getHubIdInvoke() string { return res.id } -func (self StatsSinkServer) getLastUpdateIdInvoke() (int, error) { +func (self Server) getLastUpdateIdInvoke() (int, error) { token := getLastUpdateIdToken{response: make(chan getLastUpdateIdResult, 1)} defer close(token.response) self.getLastUpdateIdChan <- token @@ -126,7 +126,7 @@ func (self StatsSinkServer) getLastUpdateIdInvoke() (int, error) { return res.id, res.err } -func (self StatsSinkServer) Close() { +func (self Server) Close() { self.quit <- true <-self.done close(self.quit) @@ -140,9 +140,9 @@ func (self StatsSinkServer) Close() { self.store.Close() } -func NewServer(dbPath string) (server *StatsSinkServer, err error) { +func NewServer(dbPath string) (server *Server, err error) { // TODO read configuration and create instance with correct settings - server = new(StatsSinkServer) + server = new(Server) server.store, err = NewStore(dbPath) if err != nil { return @@ -150,7 +150,7 @@ func NewServer(dbPath string) (server *StatsSinkServer, err error) { server.quit = make(chan bool) server.done = make(chan bool) - server.appendData = make(chan StatisticsData, 5) + server.appendData = make(chan DataUpdateFull, 5) server.appendManyData = make(chan appendManyToken, 5) server.getUpdatesAfterChan = make(chan getUpdatesAfterToken, 1) server.getUpdatesChan = make(chan getUpdatesToken, 3) diff --git a/src/hub/src/spreadspace.org/sfive/s5srvForward.go b/src/hub/src/spreadspace.org/sfive/s5srvForward.go index bdb0cbf..abb00fb 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvForward.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvForward.go @@ -9,7 +9,7 @@ import ( "time" ) -func findMaxId(values []StatisticsData) int { +func findMaxId(values []DataUpdateFull) int { maxId := -1 for i := range values { id := values[i].SourceHubDataUpdateId @@ -20,7 +20,7 @@ func findMaxId(values []StatisticsData) int { return maxId } -func (self StatsSinkServer) getLastUpdate(baseurl string, client *http.Client) (latestId int, storeId string, err error) { +func (self Server) getLastUpdate(baseurl string, client *http.Client) (latestId int, storeId string, err error) { storeId = self.getHubIdInvoke() var resp *http.Response @@ -58,7 +58,7 @@ func (self StatsSinkServer) getLastUpdate(baseurl string, client *http.Client) ( return } -func (self StatsSinkServer) handleForwarding(baseurl string, client *http.Client) { +func (self Server) handleForwarding(baseurl string, client *http.Client) { url := baseurl + "/updates" tryResync: for { @@ -88,7 +88,7 @@ tryResync: continue nextBatch } - data, err := json.Marshal(StatisticsDataContainer{updates}) + data, err := json.Marshal(DataUpdateFullContainer{updates}) if err != nil { s5l.Panicf("fwd: encode failed: %v\n", err) @@ -116,6 +116,6 @@ tryResync: } } -func (self StatsSinkServer) RunForwarding(forwardBaseUrl string) { +func (self Server) RunForwarding(forwardBaseUrl string) { self.handleForwarding(forwardBaseUrl, http.DefaultClient) } diff --git a/src/hub/src/spreadspace.org/sfive/s5srvForwardEs.go b/src/hub/src/spreadspace.org/sfive/s5srvForwardEs.go index da5ff80..4a4c838 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvForwardEs.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvForwardEs.go @@ -15,7 +15,7 @@ const lastUpdateJson = `{ "aggregations": { "last-id" : { "max" : { "field": "SourceHubDataUpdateId" } } } }` -func (self StatsSinkServer) getLastUpdateEs(baseurl string, client *http.Client) (latestId int, storeId string, err error) { +func (self Server) getLastUpdateEs(baseurl string, client *http.Client) (latestId int, storeId string, err error) { url := baseurl + "/dataupdate/_search?search_type=count" storeId = self.getHubIdInvoke() @@ -68,7 +68,7 @@ func (self StatsSinkServer) getLastUpdateEs(baseurl string, client *http.Client) return } -func (self StatsSinkServer) handleForwardingToElasticSearch(baseurl string, client *http.Client) { +func (self Server) handleForwardingToElasticSearch(baseurl string, client *http.Client) { url := baseurl + "/_bulk" tryResync: for { @@ -136,6 +136,6 @@ tryResync: } } -func (self StatsSinkServer) RunForwardingToElasticSearch(forwardBaseUrl string) { +func (self Server) RunForwardingToElasticSearch(forwardBaseUrl string) { self.handleForwardingToElasticSearch(forwardBaseUrl, http.DefaultClient) } diff --git a/src/hub/src/spreadspace.org/sfive/s5srvForwardGraphite.go b/src/hub/src/spreadspace.org/sfive/s5srvForwardGraphite.go index 9779960..42c16bc 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvForwardGraphite.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvForwardGraphite.go @@ -7,7 +7,7 @@ import ( "github.com/equinox0815/graphite-golang" ) -func (self StatsSinkServer) getLastUpdateGraphite(conn *graphite.Graphite) (latestId int, storeId string, err error) { +func (self Server) getLastUpdateGraphite(conn *graphite.Graphite) (latestId int, storeId string, err error) { latestId, err = self.getLastUpdateIdInvoke() if err != nil { s5l.Printf("fwd-graphite: failed to get own hubid: %v\n", err) @@ -17,7 +17,7 @@ func (self StatsSinkServer) getLastUpdateGraphite(conn *graphite.Graphite) (late return } -func (self StatsSinkServer) handleForwardingToGraphite(forwardHost string, basePath string) { +func (self Server) handleForwardingToGraphite(forwardHost string, basePath string) { tryResync: for { client, err := graphite.NewGraphiteFromAddress(forwardHost) @@ -80,6 +80,6 @@ tryResync: } } -func (self StatsSinkServer) RunForwardingToGraphite(forwardHost string, basePath string) { +func (self Server) RunForwardingToGraphite(forwardHost string, basePath string) { self.handleForwardingToGraphite(forwardHost, basePath) } diff --git a/src/hub/src/spreadspace.org/sfive/s5srvForwardPiwik.go b/src/hub/src/spreadspace.org/sfive/s5srvForwardPiwik.go index 5a25622..55e67e1 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvForwardPiwik.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvForwardPiwik.go @@ -17,7 +17,7 @@ type PiwikBulkRequest struct { TokenAuth string `json:"token_auth"` } -func (self StatsSinkServer) getLastUpdatePiwik(piwikURL, siteURL string, siteID uint, token string, client *http.Client) (latestId int, storeId string, err error) { +func (self Server) getLastUpdatePiwik(piwikURL, siteURL string, siteID uint, token string, client *http.Client) (latestId int, storeId string, err error) { // TODO: ask piwik what the last update was... latestId, err = 0, nil //self.getLastUpdateIdInvoke() @@ -29,7 +29,7 @@ func (self StatsSinkServer) getLastUpdatePiwik(piwikURL, siteURL string, siteID return } -func (self StatsSinkServer) handleForwardingToPiwik(piwikURL, siteURL string, siteID uint, token string, client *http.Client) { +func (self Server) handleForwardingToPiwik(piwikURL, siteURL string, siteID uint, token string, client *http.Client) { tryResync: for { lastId, _, err := self.getLastUpdatePiwik(piwikURL, siteURL, siteID, token, client) @@ -106,6 +106,6 @@ tryResync: } } -func (self StatsSinkServer) RunForwardingToPiwik(piwikURL, siteURL string, siteID uint, piwikToken string) { +func (self Server) RunForwardingToPiwik(piwikURL, siteURL string, siteID uint, piwikToken string) { self.handleForwardingToPiwik(piwikURL, siteURL, siteID, piwikToken, http.DefaultClient) } diff --git a/src/hub/src/spreadspace.org/sfive/s5srvPipe.go b/src/hub/src/spreadspace.org/sfive/s5srvPipe.go index efc190c..1a4d6a2 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvPipe.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvPipe.go @@ -6,7 +6,7 @@ import ( "net" ) -func (self StatsSinkServer) handleConnection(conn net.Conn) { +func (self Server) handleConnection(conn net.Conn) { reader := bufio.NewReader(conn) buffer, err := reader.ReadBytes('\n') if err != nil { @@ -42,7 +42,7 @@ func (self StatsSinkServer) handleConnection(conn net.Conn) { } } -func (self StatsSinkServer) handlePacketConn(pconn net.PacketConn) { +func (self Server) handlePacketConn(pconn net.PacketConn) { decoder := NewPlainDecoder() buffer := make([]byte, 64*1024) for { @@ -61,7 +61,7 @@ func (self StatsSinkServer) handlePacketConn(pconn net.PacketConn) { } } -func (self StatsSinkServer) ServePipe(pipePath string) { +func (self Server) ServePipe(pipePath string) { ln, err := net.Listen("unix", pipePath) if err != nil { s5l.Printf("pipe: failed to connect: %v", err) @@ -80,7 +80,7 @@ func (self StatsSinkServer) ServePipe(pipePath string) { } } -func (self StatsSinkServer) ServeGramPipe(pipePath string) { +func (self Server) ServeGramPipe(pipePath string) { pconn, err := net.ListenPacket("unixgram", pipePath) if err != nil { s5l.Printf("p-pipe: failed to listen: %v", err) diff --git a/src/hub/src/spreadspace.org/sfive/s5srvWeb.go b/src/hub/src/spreadspace.org/sfive/s5srvWeb.go index a474f47..efe4718 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvWeb.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvWeb.go @@ -12,12 +12,12 @@ import ( "github.com/zenazn/goji/web" ) -func (self StatsSinkServer) healthz(c web.C, w http.ResponseWriter, r *http.Request) { +func (self Server) healthz(c web.C, w http.ResponseWriter, r *http.Request) { // TODO: do a more sophisticated check fmt.Fprintf(w, "%s\n", self.store.GetStoreId()) } -func (self StatsSinkServer) getSourcesList(c web.C, w http.ResponseWriter, r *http.Request) { +func (self Server) getSourcesList(c web.C, w http.ResponseWriter, r *http.Request) { const resourceName = "sources" values, err := self.store.GetSources() if err != nil { @@ -32,7 +32,7 @@ func (self StatsSinkServer) getSourcesList(c web.C, w http.ResponseWriter, r *ht fmt.Fprintf(w, "%s", jsonString) } -func (self StatsSinkServer) getSource(c web.C, w http.ResponseWriter, r *http.Request) { +func (self Server) getSource(c web.C, w http.ResponseWriter, r *http.Request) { const resourceName = "source" id, err := strconv.ParseInt(c.URLParams["id"], 10, 64) if err != nil { @@ -56,7 +56,7 @@ func (self StatsSinkServer) getSource(c web.C, w http.ResponseWriter, r *http.Re fmt.Fprintf(w, "%s", jsonString) } -func (self StatsSinkServer) getUpdateList(c web.C, w http.ResponseWriter, r *http.Request) { +func (self Server) getUpdateList(c web.C, w http.ResponseWriter, r *http.Request) { const resourceName = "updates" values, err := self.getUpdatesInvoke() if err != nil { @@ -71,7 +71,7 @@ func (self StatsSinkServer) getUpdateList(c web.C, w http.ResponseWriter, r *htt fmt.Fprintf(w, "%s", jsonString) } -func (self StatsSinkServer) getUpdate(c web.C, w http.ResponseWriter, r *http.Request) { +func (self Server) getUpdate(c web.C, w http.ResponseWriter, r *http.Request) { const resourceName = "update" id, err := strconv.ParseInt(c.URLParams["id"], 10, 64) if err != nil { @@ -91,7 +91,7 @@ func (self StatsSinkServer) getUpdate(c web.C, w http.ResponseWriter, r *http.Re fmt.Fprintf(w, "%s", jsonString) } -func (self StatsSinkServer) postUpdate(c web.C, w http.ResponseWriter, r *http.Request) { +func (self Server) postUpdate(c web.C, w http.ResponseWriter, r *http.Request) { const resourceName = "update" decoder := NewPlainDecoder() @@ -102,7 +102,7 @@ func (self StatsSinkServer) postUpdate(c web.C, w http.ResponseWriter, r *http.R return } - container := StatisticsDataContainer{} + container := DataUpdateFullContainer{} err = json.Unmarshal(buffer, &container) if err == nil { token := appendManyToken{ @@ -129,7 +129,7 @@ func (self StatsSinkServer) postUpdate(c web.C, w http.ResponseWriter, r *http.R // TODO send response channel, wait for OK } -func (self StatsSinkServer) getLastUpdateIdForUuid(c web.C, w http.ResponseWriter, r *http.Request) { +func (self Server) getLastUpdateIdForUuid(c web.C, w http.ResponseWriter, r *http.Request) { const resourceName = "lastupdateid" id := c.URLParams["id"] value, err := self.store.GetLastUpdateForUuid(id) @@ -140,7 +140,7 @@ func (self StatsSinkServer) getLastUpdateIdForUuid(c web.C, w http.ResponseWrite fmt.Fprintf(w, "%d", value) } -func (self StatsSinkServer) ServeWeb(vizAppLocation string) { +func (self Server) ServeWeb(vizAppLocation string) { if _, err := os.Stat(vizAppLocation); err != nil { if os.IsNotExist(err) { s5l.Panicf("web: viz-app at %s does not exist.", vizAppLocation) diff --git a/src/hub/src/spreadspace.org/sfive/s5store.go b/src/hub/src/spreadspace.org/sfive/s5store.go index 1339a4b..4845660 100644 --- a/src/hub/src/spreadspace.org/sfive/s5store.go +++ b/src/hub/src/spreadspace.org/sfive/s5store.go @@ -58,7 +58,7 @@ func initDb(boltPath string) (boltDb *bolt.DB, hubId string, err error) { return } -func updateFromStatisticsData(value StatisticsData) (dataUpdateDb, []ClientData, sourceDb) { +func updateFromDataUpdateFull(value DataUpdateFull) (dataUpdateDb, []ClientData, sourceDb) { du := NewDataUpdateDb(value) cd := value.Data.Clients src := NewSourceDb(value) @@ -164,10 +164,10 @@ func (s Store) appendItem(tx *bolt.Tx, du dataUpdateDb, cd []ClientData, src sou return } -func (s Store) AppendMany(updates []StatisticsData) (err error) { +func (s Store) AppendMany(updates []DataUpdateFull) (err error) { return s.db.Update(func(tx *bolt.Tx) error { for _, update := range updates { - du, cd, src := updateFromStatisticsData(update) + du, cd, src := updateFromDataUpdateFull(update) if err := s.appendItem(tx, du, cd, src); err != nil { return err } @@ -176,8 +176,8 @@ func (s Store) AppendMany(updates []StatisticsData) (err error) { }) } -func (s Store) Append(update StatisticsData) (err error) { - return s.AppendMany([]StatisticsData{update}) +func (s Store) Append(update DataUpdateFull) (err error) { + return s.AppendMany([]DataUpdateFull{update}) } func (s Store) getSource(tx *bolt.Tx, id int) (res sourceDb, err error) { @@ -253,7 +253,7 @@ func (s Store) getClientsByUpdateId(tx *bolt.Tx, id int) (res []ClientData, err return } -func (s Store) CreateStatisticsDataFrom(tx *bolt.Tx, duId int, dat dataUpdateDb) (res StatisticsData, err error) { +func (s Store) CreateDataUpdateFullFromDb(tx *bolt.Tx, duId int, dat dataUpdateDb) (res DataUpdateFull, err error) { var clients []ClientData if clients, err = s.getClientsByUpdateId(tx, duId); err != nil { return @@ -273,13 +273,13 @@ func (s Store) CreateStatisticsDataFrom(tx *bolt.Tx, duId int, dat dataUpdateDb) return } -func (s Store) GetUpdatesAfter(id int) (res []StatisticsData, err error) { +func (s Store) GetUpdatesAfter(id int) (res []DataUpdateFull, err error) { // err = s.db.View(func(tx *bolt.Tx) error { // // TODO: iterate over ids // duId := 1 // for i := range dat { - // sd, err := s.CreateStatisticsDataFrom(tx, duId, du) + // sd, err := s.CreateDataUpdateFullFromDb(tx, duId, du) // if err != nil { // return err // } @@ -290,7 +290,7 @@ func (s Store) GetUpdatesAfter(id int) (res []StatisticsData, err error) { return } -func (s Store) GetUpdates() (res []StatisticsData, err error) { +func (s Store) GetUpdates() (res []DataUpdateFull, err error) { return s.GetUpdatesAfter(-1) } diff --git a/src/hub/src/spreadspace.org/sfive/s5store_test.go b/src/hub/src/spreadspace.org/sfive/s5store_test.go index 38e7bf7..7b45e59 100644 --- a/src/hub/src/spreadspace.org/sfive/s5store_test.go +++ b/src/hub/src/spreadspace.org/sfive/s5store_test.go @@ -34,7 +34,7 @@ func TestAppend(t *testing.T) { update := DataUpdate{Data: SourceData{BytesSent: 1, ClientCount: 3, BytesReceived: 1}, StartTime: startTime, Duration: 5000} streamId := StreamId{ContentId: "content", Format: "7bitascii", Quality: QualityHigh} source := SourceId{Hostname: "localhost", Tags: []string{"tag1", "master"}, StreamId: streamId, Version: 1} - dat := StatisticsData{"", -1, source, update} + dat := DataUpdateFull{"", -1, source, update} err = store.Append(dat) if err != nil { @@ -56,7 +56,7 @@ func TestGetUpdatesAfter(t *testing.T) { update := DataUpdate{Data: SourceData{BytesSent: 1, ClientCount: 3, BytesReceived: 1}, StartTime: startTime, Duration: 5000} streamId := StreamId{ContentId: "content", Format: "7bitascii", Quality: QualityHigh} source := SourceId{Hostname: "localhost", Tags: []string{"tag1", "master"}, StreamId: streamId, Version: 1} - dat := StatisticsData{"", -1, source, update} + dat := DataUpdateFull{"", -1, source, update} err = store.Append(dat) if err != nil { @@ -68,7 +68,7 @@ func TestGetUpdatesAfter(t *testing.T) { t.Logf("got updates (err %v):\n%#v", err, res) } -func generateStatisticsData(n int) (data []StatisticsData) { +func generateDataUpdateFull(n int) (data []DataUpdateFull) { hostnames := []string{"streamer1", "streamer2"} contents := []string{"av", "audio"} formats := []string{"webm", "flash", "hls"} @@ -91,7 +91,7 @@ func generateStatisticsData(n int) (data []StatisticsData) { for _, content := range contents { for _, format := range formats { for _, quality := range qualities { - d := StatisticsData{} + d := DataUpdateFull{} d.SourceId.Version = 1 d.SourceId.Hostname = hostname d.SourceId.Tags = tags @@ -126,7 +126,7 @@ func BenchmarkAppendMany(b *testing.B) { b.Errorf("Failed to initialize: %v", err) } defer store.Close() - data := generateStatisticsData(b.N) + data := generateDataUpdateFull(b.N) b.ResetTimer() @@ -142,7 +142,7 @@ func BenchmarkGetUpdatesAfter(b *testing.B) { b.Errorf("Failed to initialize: %v", err) } defer store.Close() - data := generateStatisticsData(b.N) + data := generateDataUpdateFull(b.N) if err := store.AppendMany(data); err != nil { b.Errorf("Failed to append: %v", err) } diff --git a/src/hub/src/spreadspace.org/sfive/s5typesApi.go b/src/hub/src/spreadspace.org/sfive/s5typesApi.go index ad6deaf..ae99b03 100644 --- a/src/hub/src/spreadspace.org/sfive/s5typesApi.go +++ b/src/hub/src/spreadspace.org/sfive/s5typesApi.go @@ -40,7 +40,7 @@ type DataUpdate struct { Data SourceData `json:"data"` } -type StatisticsData struct { +type DataUpdateFull struct { SourceHubUuid string `json:"SourceHubUuid,omitempty"` SourceHubDataUpdateId int `json:"SourceHubDataUpdateId,omitempty"` SourceId @@ -51,18 +51,18 @@ type DataContainer struct { Data interface{} `json:"data"` } -type StatisticsDataContainer struct { - Data []StatisticsData `json:"data"` +type DataUpdateFullContainer struct { + Data []DataUpdateFull `json:"data"` } -func (self *StatisticsData) CopyFromSourceId(id *SourceId) { +func (self *DataUpdateFull) CopyFromSourceId(id *SourceId) { self.Hostname = id.Hostname self.StreamId = id.StreamId self.Tags = id.Tags self.Version = id.Version } -func (self *StatisticsData) CopyFromUpdate(id *DataUpdate) { +func (self *DataUpdateFull) CopyFromUpdate(id *DataUpdate) { self.StartTime = id.StartTime self.Duration = id.Duration self.Data = id.Data diff --git a/src/hub/src/spreadspace.org/sfive/s5typesStore.go b/src/hub/src/spreadspace.org/sfive/s5typesStore.go index d6de68e..ccbc864 100644 --- a/src/hub/src/spreadspace.org/sfive/s5typesStore.go +++ b/src/hub/src/spreadspace.org/sfive/s5typesStore.go @@ -39,7 +39,7 @@ type sourceDb struct { Tags []string `json:"t"` } -func NewSourceDb(value StatisticsData) sourceDb { +func NewSourceDb(value DataUpdateFull) sourceDb { return sourceDb{ Hostname: value.SourceId.Hostname, StreamId: streamIdDb{ @@ -70,8 +70,7 @@ type clientDataDb struct { BytesSent uint `json:"bs"` } -// stored in dataUpdatesTn -// in DB, StatisticsData/DataUpdate is flattened compared to JSON DTOs +// stored in dataUpdatesBn type dataUpdateDb struct { SourceHubUuid string `json:"h,omitempty"` SourceHubDataUpdateId int `json:"hi,omitempty"` @@ -83,7 +82,7 @@ type dataUpdateDb struct { BytesSent uint `json:"bs"` } -func NewDataUpdateDb(v StatisticsData) dataUpdateDb { +func NewDataUpdateDb(v DataUpdateFull) dataUpdateDb { return dataUpdateDb{ v.SourceHubUuid, v.SourceHubDataUpdateId, @@ -96,7 +95,7 @@ func NewDataUpdateDb(v StatisticsData) dataUpdateDb { } } -func (s *StatisticsData) CopyFromDataUpdateDb(v dataUpdateDb, vId int, hubId string) { +func (s *DataUpdateFull) CopyFromDataUpdateDb(v dataUpdateDb, vId int, hubId string) { if v.SourceHubUuid == "" { s.SourceHubUuid = hubId s.SourceHubDataUpdateId = vId -- cgit v1.2.3 From 9e51b380909dea3149cb0d3fdc57090d5153e04c Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Thu, 27 Apr 2017 04:04:10 +0200 Subject: storing data updates into bolt works now --- src/hub/src/spreadspace.org/sfive/s5srvWeb.go | 17 +++- src/hub/src/spreadspace.org/sfive/s5store.go | 137 +++++++++++++++----------- 2 files changed, 94 insertions(+), 60 deletions(-) (limited to 'src') diff --git a/src/hub/src/spreadspace.org/sfive/s5srvWeb.go b/src/hub/src/spreadspace.org/sfive/s5srvWeb.go index efe4718..6d3af91 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvWeb.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvWeb.go @@ -80,7 +80,11 @@ func (self Server) getUpdate(c web.C, w http.ResponseWriter, r *http.Request) { } value, err := self.store.GetUpdate(int(id)) if err != nil { - http.Error(w, fmt.Sprintf("failed to retrieve %s: %v", resourceName, err), http.StatusInternalServerError) + if err == ErrNotFound { + http.Error(w, fmt.Sprintf("failed to retrieve %s: %v", resourceName, err), http.StatusNotFound) + } else { + http.Error(w, fmt.Sprintf("failed to retrieve %s: %v", resourceName, err), http.StatusInternalServerError) + } return } jsonString, err := json.Marshal(value) @@ -129,6 +133,16 @@ func (self Server) postUpdate(c web.C, w http.ResponseWriter, r *http.Request) { // TODO send response channel, wait for OK } +func (self Server) getLastUpdateId(c web.C, w http.ResponseWriter, r *http.Request) { + const resourceName = "lastupdateid" + value, err := self.store.GetLastUpdateId() + if err != nil { + http.Error(w, fmt.Sprintf("failed to retrieve %s: %v", resourceName, err), http.StatusInternalServerError) + return + } + fmt.Fprintf(w, "%d", value) +} + func (self Server) getLastUpdateIdForUuid(c web.C, w http.ResponseWriter, r *http.Request) { const resourceName = "lastupdateid" id := c.URLParams["id"] @@ -155,6 +169,7 @@ func (self Server) ServeWeb(vizAppLocation string) { goji.Get("/updates", self.getUpdateList) goji.Get("/updates/:id", self.getUpdate) goji.Post("/updates", self.postUpdate) + goji.Get("/lastupdate", self.getLastUpdateId) goji.Get("/lastupdate/:id", self.getLastUpdateIdForUuid) goji.Handle("/viz/*", http.StripPrefix("/viz/", http.FileServer(http.Dir(vizAppLocation)))) diff --git a/src/hub/src/spreadspace.org/sfive/s5store.go b/src/hub/src/spreadspace.org/sfive/s5store.go index 4845660..64cb879 100644 --- a/src/hub/src/spreadspace.org/sfive/s5store.go +++ b/src/hub/src/spreadspace.org/sfive/s5store.go @@ -58,11 +58,11 @@ func initDb(boltPath string) (boltDb *bolt.DB, hubId string, err error) { return } -func updateFromDataUpdateFull(value DataUpdateFull) (dataUpdateDb, []ClientData, sourceDb) { +func updateFromDataUpdateFull(value DataUpdateFull) (sourceDb, dataUpdateDb, []ClientData) { + src := NewSourceDb(value) du := NewDataUpdateDb(value) cd := value.Data.Clients - src := NewSourceDb(value) - return du, cd, src + return src, du, cd } func (s Store) insertNewSource(tx *bolt.Tx, src sourceDb) (srcId int, err error) { @@ -94,8 +94,18 @@ func (s Store) insertNewSource(tx *bolt.Tx, src sourceDb) (srcId int, err error) return srcId, err } -func (s Store) insertDataUpdateEntry(tx *bolt.Tx, srcId int, du *dataUpdateDb) (duId int, err error) { - // TODO: add me +func (s Store) insertDataUpdate(tx *bolt.Tx, du dataUpdateDb) (duId int, err error) { + b := tx.Bucket([]byte(dataUpdatesBn)) + b.FillPercent = 1.0 // we only do appends + + next, _ := b.NextSequence() + duId = int(next) + + var jsonData []byte + if jsonData, err = json.Marshal(du); err != nil { + return + } + err = b.Put(itob(duId), jsonData) return } @@ -122,7 +132,7 @@ func (s Store) insertNewUserAgent(tx *bolt.Tx, ua string) (uaId int, err error) return uaId, err } -func (s Store) insertDataUpdateClientEntries(tx *bolt.Tx, duId int, cd []ClientData) error { +func (s Store) insertClientData(tx *bolt.Tx, duId int, cd []ClientData) error { if len(cd) == 0 { return nil } @@ -146,18 +156,17 @@ func (s Store) insertDataUpdateClientEntries(tx *bolt.Tx, duId int, cd []ClientD return b.Put(itob(duId), jsonData) } -func (s Store) appendItem(tx *bolt.Tx, du dataUpdateDb, cd []ClientData, src sourceDb) (err error) { - var srcId int - if srcId, err = s.insertNewSource(tx, src); err != nil { +func (s Store) appendItem(tx *bolt.Tx, src sourceDb, du dataUpdateDb, cd []ClientData) (err error) { + if du.SourceId, err = s.insertNewSource(tx, src); err != nil { return } var duId int - if duId, err = s.insertDataUpdateEntry(tx, srcId, &du); err != nil { + if duId, err = s.insertDataUpdate(tx, du); err != nil { return } - if err = s.insertDataUpdateClientEntries(tx, duId, cd); err != nil { + if err = s.insertClientData(tx, duId, cd); err != nil { return } @@ -167,8 +176,8 @@ func (s Store) appendItem(tx *bolt.Tx, du dataUpdateDb, cd []ClientData, src sou func (s Store) AppendMany(updates []DataUpdateFull) (err error) { return s.db.Update(func(tx *bolt.Tx) error { for _, update := range updates { - du, cd, src := updateFromDataUpdateFull(update) - if err := s.appendItem(tx, du, cd, src); err != nil { + src, du, cd := updateFromDataUpdateFull(update) + if err := s.appendItem(tx, src, du, cd); err != nil { return err } } @@ -197,8 +206,7 @@ func (s Store) getSource(tx *bolt.Tx, id int) (res sourceDb, err error) { func (s Store) GetSources() (res []SourceId, err error) { res = []SourceId{} err = s.db.View(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte(sourcesRevBn)) - c := b.Cursor() + c := tx.Bucket([]byte(sourcesRevBn)).Cursor() for k, v := c.First(); k != nil; k, v = c.Next() { var s sourceDb if err := json.Unmarshal(v, &s); err != nil { @@ -225,11 +233,6 @@ func (s Store) GetSource(id int) (res SourceId, err error) { return } -func (s Store) GetUpdate(id int) (res dataUpdateDb, err error) { - // TODO: implement me - return -} - func (s Store) getClientsByUpdateId(tx *bolt.Tx, id int) (res []ClientData, err error) { bc := tx.Bucket([]byte(clientDataBn)) bu := tx.Bucket([]byte(userAgentsRevBn)) @@ -253,17 +256,17 @@ func (s Store) getClientsByUpdateId(tx *bolt.Tx, id int) (res []ClientData, err return } -func (s Store) CreateDataUpdateFullFromDb(tx *bolt.Tx, duId int, dat dataUpdateDb) (res DataUpdateFull, err error) { +func (s Store) CreateDataUpdateFullFromDb(tx *bolt.Tx, duId int, du dataUpdateDb) (res DataUpdateFull, err error) { var clients []ClientData if clients, err = s.getClientsByUpdateId(tx, duId); err != nil { return } var src sourceDb - if src, err = s.getSource(tx, dat.SourceId); err != nil { + if src, err = s.getSource(tx, du.SourceId); err != nil { return } - res.CopyFromDataUpdateDb(dat, duId, s.hubId) + res.CopyFromDataUpdateDb(du, duId, s.hubId) res.Hostname = src.Hostname res.StreamId.ContentId = src.StreamId.ContentId res.StreamId.Format = src.StreamId.Format @@ -274,59 +277,75 @@ func (s Store) CreateDataUpdateFullFromDb(tx *bolt.Tx, duId int, dat dataUpdateD } func (s Store) GetUpdatesAfter(id int) (res []DataUpdateFull, err error) { - // err = s.db.View(func(tx *bolt.Tx) error { - // // TODO: iterate over ids - // duId := 1 - - // for i := range dat { - // sd, err := s.CreateDataUpdateFullFromDb(tx, duId, du) - // if err != nil { - // return err - // } - // res = append(res, sd) - // } - // return nil - // }) + if id < 0 { + id = 0 + } + err = s.db.View(func(tx *bolt.Tx) error { + c := tx.Bucket([]byte(dataUpdatesBn)).Cursor() + if k, _ := c.Seek(itob(id)); k == nil { + return nil + } + for k, v := c.Next(); k != nil; k, v = c.Next() { + var d dataUpdateDb + if err := json.Unmarshal(v, &d); err != nil { + return err + } + + var duf DataUpdateFull + duf, err := s.CreateDataUpdateFullFromDb(tx, btoi(k), d) + if err != nil { + return err + } + res = append(res, duf) + } + return nil + }) return } func (s Store) GetUpdates() (res []DataUpdateFull, err error) { + // TODO: implement from:to with limit return s.GetUpdatesAfter(-1) } +func (s Store) GetUpdate(id int) (res DataUpdateFull, err error) { + err = s.db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(dataUpdatesBn)) + + jsonData := b.Get(itob(id)) + if jsonData == nil { + return ErrNotFound + } + + var d dataUpdateDb + if err := json.Unmarshal(jsonData, &d); err != nil { + return err + } + + var err error + if res, err = s.CreateDataUpdateFullFromDb(tx, id, d); err != nil { + return err + } + return nil + }) + return +} + type lastUpdateQueryResult struct { MaxDataUpdateId *int } func (s Store) GetLastUpdateForUuid(uuid string) (updateId int, err error) { // TODO: implement me! - updateId = -1 - - // result := lastUpdateQueryResult{} - // err = s.db.SelectOne( - // &result, - // "select max(SourceHubDataUpdateId) as MaxDataUpdateId from "+dataUpdatesTn+" where SourceHubUuid = ?", - // uuid) - // if err == nil { - // updateId = result.MaxDataUpdateId - // } else { - // s5l.Printf("db: failed to find max SourceHubDataUpdateId for %s: %v", uuid, err) - // } - // return + updateId = 0 return } func (s Store) GetLastUpdateId() (updateId int, err error) { - // TODO: implement me! - updateId = -1 - - // result := lastUpdateQueryResult{} - // err = s.db.SelectOne(&result, "select max(Id) as MaxDataUpdateId from "+dataUpdatesTn) - // if err == nil { - // updateId = result.MaxDataUpdateId - // } else { - // s5l.Printf("db: failed to find max DataUpdateId: %v", err) - // } + err = s.db.View(func(tx *bolt.Tx) error { + updateId = int(tx.Bucket([]byte(dataUpdatesBn)).Sequence()) + return nil + }) return } -- cgit v1.2.3 From 5e95861eb81e45ebb7b58de6f31cde929dde8211 Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Thu, 27 Apr 2017 05:09:24 +0200 Subject: no more self --- src/hub/src/spreadspace.org/sfive/s5cvt.go | 8 +-- src/hub/src/spreadspace.org/sfive/s5srv.go | 70 +++++++++++----------- src/hub/src/spreadspace.org/sfive/s5srvForward.go | 14 ++--- .../src/spreadspace.org/sfive/s5srvForwardEs.go | 14 ++--- .../spreadspace.org/sfive/s5srvForwardGraphite.go | 14 ++--- .../src/spreadspace.org/sfive/s5srvForwardPiwik.go | 14 ++--- src/hub/src/spreadspace.org/sfive/s5srvPipe.go | 16 ++--- src/hub/src/spreadspace.org/sfive/s5srvWeb.go | 52 ++++++++-------- src/hub/src/spreadspace.org/sfive/s5typesApi.go | 18 +++--- 9 files changed, 110 insertions(+), 110 deletions(-) (limited to 'src') diff --git a/src/hub/src/spreadspace.org/sfive/s5cvt.go b/src/hub/src/spreadspace.org/sfive/s5cvt.go index 12c0562..dd326fb 100644 --- a/src/hub/src/spreadspace.org/sfive/s5cvt.go +++ b/src/hub/src/spreadspace.org/sfive/s5cvt.go @@ -38,20 +38,20 @@ func NewPlainDecoder() FullDecoder { return new(PlainDecoder) } -func (self *StatefulDecoder) Decode(jsonString []byte) (dat DataUpdateFull, err error) { - dat.CopyFromSourceId(&self.sourceId) +func (sd *StatefulDecoder) Decode(jsonString []byte) (dat DataUpdateFull, err error) { + dat.CopyFromSourceId(&sd.sourceId) err = json.Unmarshal(jsonString, &dat) // like in PlainDecoder, let the client decide how to use partial results // (Unmarshal returns partial results in case of errors) return } -func (self *PlainDecoder) Decode(jsonString []byte) (dat DataUpdateFull, err error) { +func (pd *PlainDecoder) Decode(jsonString []byte) (dat DataUpdateFull, err error) { err = json.Unmarshal(jsonString, &dat) return } -func (self *PlainEncoder) Encode(data *DataUpdateFull) []byte { +func (pe *PlainEncoder) Encode(data *DataUpdateFull) []byte { res, err := json.Marshal(data) if err != nil { s5l.Panicln("failed to encode DataUpdateFull") diff --git a/src/hub/src/spreadspace.org/sfive/s5srv.go b/src/hub/src/spreadspace.org/sfive/s5srv.go index 047a318..ac3e086 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srv.go +++ b/src/hub/src/spreadspace.org/sfive/s5srv.go @@ -52,16 +52,16 @@ type Server struct { getLastUpdateIdChan chan getLastUpdateIdToken } -func (self Server) appendActor() { - defer func() { self.done <- true }() +func (srv Server) appendActor() { + defer func() { srv.done <- true }() for { select { - case <-self.quit: + case <-srv.quit: return - case value := <-self.appendData: + case value := <-srv.appendData: var err error for tryNum := 0; tryNum < 5; tryNum++ { - err = self.store.Append(value) + err = srv.store.Append(value) if err != nil { time.Sleep(1 * time.Second) } else { @@ -71,73 +71,73 @@ func (self Server) appendActor() { if err != nil { s5l.Printf("failed to store data: %v\n", err) } - case token := <-self.appendManyData: - err := self.store.AppendMany(token.data) + case token := <-srv.appendManyData: + err := srv.store.AppendMany(token.data) if err != nil { s5l.Printf("failed to store many data: %v\n", err) token.response <- false } else { token.response <- true } - case token := <-self.getUpdatesAfterChan: - values, err := self.store.GetUpdatesAfter(token.id) + case token := <-srv.getUpdatesAfterChan: + values, err := srv.store.GetUpdatesAfter(token.id) token.response <- getUpdatesResult{values, err} - case token := <-self.getUpdatesChan: - values, err := self.store.GetUpdates() + case token := <-srv.getUpdatesChan: + values, err := srv.store.GetUpdates() token.response <- getUpdatesResult{values, err} - case token := <-self.getHubIdChan: - token.response <- getHubIdResult{self.store.GetStoreId()} - case token := <-self.getLastUpdateIdChan: - lastUpdateId, err := self.store.GetLastUpdateId() + case token := <-srv.getHubIdChan: + token.response <- getHubIdResult{srv.store.GetStoreId()} + case token := <-srv.getLastUpdateIdChan: + lastUpdateId, err := srv.store.GetLastUpdateId() token.response <- getLastUpdateIdResult{lastUpdateId, err} } } } -func (self Server) getUpdatesAfterInvoke(id int) ([]DataUpdateFull, error) { +func (srv Server) getUpdatesAfterInvoke(id int) ([]DataUpdateFull, error) { token := getUpdatesAfterToken{id: id, response: make(chan getUpdatesResult, 1)} defer close(token.response) - self.getUpdatesAfterChan <- token + srv.getUpdatesAfterChan <- token res := <-token.response return res.values, res.err } -func (self Server) getUpdatesInvoke() ([]DataUpdateFull, error) { +func (srv Server) getUpdatesInvoke() ([]DataUpdateFull, error) { token := getUpdatesToken{response: make(chan getUpdatesResult, 1)} defer close(token.response) - self.getUpdatesChan <- token + srv.getUpdatesChan <- token res := <-token.response return res.values, res.err } -func (self Server) getHubIdInvoke() string { +func (srv Server) getHubIdInvoke() string { token := getHubIdToken{response: make(chan getHubIdResult, 1)} defer close(token.response) - self.getHubIdChan <- token + srv.getHubIdChan <- token res := <-token.response return res.id } -func (self Server) getLastUpdateIdInvoke() (int, error) { +func (srv Server) getLastUpdateIdInvoke() (int, error) { token := getLastUpdateIdToken{response: make(chan getLastUpdateIdResult, 1)} defer close(token.response) - self.getLastUpdateIdChan <- token + srv.getLastUpdateIdChan <- token res := <-token.response return res.id, res.err } -func (self Server) Close() { - self.quit <- true - <-self.done - close(self.quit) - close(self.done) - close(self.appendData) - close(self.appendManyData) - close(self.getUpdatesAfterChan) - close(self.getUpdatesChan) - close(self.getHubIdChan) - close(self.getLastUpdateIdChan) - self.store.Close() +func (srv Server) Close() { + srv.quit <- true + <-srv.done + close(srv.quit) + close(srv.done) + close(srv.appendData) + close(srv.appendManyData) + close(srv.getUpdatesAfterChan) + close(srv.getUpdatesChan) + close(srv.getHubIdChan) + close(srv.getLastUpdateIdChan) + srv.store.Close() } func NewServer(dbPath string) (server *Server, err error) { diff --git a/src/hub/src/spreadspace.org/sfive/s5srvForward.go b/src/hub/src/spreadspace.org/sfive/s5srvForward.go index abb00fb..6c9823f 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvForward.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvForward.go @@ -20,8 +20,8 @@ func findMaxId(values []DataUpdateFull) int { return maxId } -func (self Server) getLastUpdate(baseurl string, client *http.Client) (latestId int, storeId string, err error) { - storeId = self.getHubIdInvoke() +func (srv Server) getLastUpdate(baseurl string, client *http.Client) (latestId int, storeId string, err error) { + storeId = srv.getHubIdInvoke() var resp *http.Response resp, err = client.Get(baseurl + "/lastupdate/" + storeId) @@ -58,11 +58,11 @@ func (self Server) getLastUpdate(baseurl string, client *http.Client) (latestId return } -func (self Server) handleForwarding(baseurl string, client *http.Client) { +func (srv Server) handleForwarding(baseurl string, client *http.Client) { url := baseurl + "/updates" tryResync: for { - lastId, _, err := self.getLastUpdate(baseurl, client) + lastId, _, err := srv.getLastUpdate(baseurl, client) if err != nil { s5l.Printf("fwd: lastupdate returned err: %v", err) @@ -74,7 +74,7 @@ tryResync: nextBatch: for { - updates, err := self.getUpdatesAfterInvoke(lastId) + updates, err := srv.getUpdatesAfterInvoke(lastId) if err != nil { s5l.Printf("fwd: failed reading updates: %v\n", err) time.Sleep(500 * time.Millisecond) @@ -116,6 +116,6 @@ tryResync: } } -func (self Server) RunForwarding(forwardBaseUrl string) { - self.handleForwarding(forwardBaseUrl, http.DefaultClient) +func (srv Server) RunForwarding(forwardBaseUrl string) { + srv.handleForwarding(forwardBaseUrl, http.DefaultClient) } diff --git a/src/hub/src/spreadspace.org/sfive/s5srvForwardEs.go b/src/hub/src/spreadspace.org/sfive/s5srvForwardEs.go index 4a4c838..622b307 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvForwardEs.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvForwardEs.go @@ -15,10 +15,10 @@ const lastUpdateJson = `{ "aggregations": { "last-id" : { "max" : { "field": "SourceHubDataUpdateId" } } } }` -func (self Server) getLastUpdateEs(baseurl string, client *http.Client) (latestId int, storeId string, err error) { +func (srv Server) getLastUpdateEs(baseurl string, client *http.Client) (latestId int, storeId string, err error) { url := baseurl + "/dataupdate/_search?search_type=count" - storeId = self.getHubIdInvoke() + storeId = srv.getHubIdInvoke() queryJson := fmt.Sprintf(lastUpdateJson, storeId) s5tl.Printf("fwd-es: query: %s", queryJson) @@ -68,11 +68,11 @@ func (self Server) getLastUpdateEs(baseurl string, client *http.Client) (latestI return } -func (self Server) handleForwardingToElasticSearch(baseurl string, client *http.Client) { +func (srv Server) handleForwardingToElasticSearch(baseurl string, client *http.Client) { url := baseurl + "/_bulk" tryResync: for { - lastId, _, err := self.getLastUpdateEs(baseurl, client) + lastId, _, err := srv.getLastUpdateEs(baseurl, client) if err != nil { s5l.Printf("fwd-es: lastupdate returned err: %v", err) time.Sleep(5 * time.Second) @@ -82,7 +82,7 @@ tryResync: nextBatch: for { - updates, err := self.getUpdatesAfterInvoke(lastId) + updates, err := srv.getUpdatesAfterInvoke(lastId) if err != nil { s5l.Printf("fwd-es: failed reading updates: %v\n", err) time.Sleep(500 * time.Millisecond) @@ -136,6 +136,6 @@ tryResync: } } -func (self Server) RunForwardingToElasticSearch(forwardBaseUrl string) { - self.handleForwardingToElasticSearch(forwardBaseUrl, http.DefaultClient) +func (srv Server) RunForwardingToElasticSearch(forwardBaseUrl string) { + srv.handleForwardingToElasticSearch(forwardBaseUrl, http.DefaultClient) } diff --git a/src/hub/src/spreadspace.org/sfive/s5srvForwardGraphite.go b/src/hub/src/spreadspace.org/sfive/s5srvForwardGraphite.go index 42c16bc..8ab0ac6 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvForwardGraphite.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvForwardGraphite.go @@ -7,8 +7,8 @@ import ( "github.com/equinox0815/graphite-golang" ) -func (self Server) getLastUpdateGraphite(conn *graphite.Graphite) (latestId int, storeId string, err error) { - latestId, err = self.getLastUpdateIdInvoke() +func (srv Server) getLastUpdateGraphite(conn *graphite.Graphite) (latestId int, storeId string, err error) { + latestId, err = srv.getLastUpdateIdInvoke() if err != nil { s5l.Printf("fwd-graphite: failed to get own hubid: %v\n", err) return @@ -17,7 +17,7 @@ func (self Server) getLastUpdateGraphite(conn *graphite.Graphite) (latestId int, return } -func (self Server) handleForwardingToGraphite(forwardHost string, basePath string) { +func (srv Server) handleForwardingToGraphite(forwardHost string, basePath string) { tryResync: for { client, err := graphite.NewGraphiteFromAddress(forwardHost) @@ -27,7 +27,7 @@ tryResync: continue tryResync } - lastId, _, err := self.getLastUpdateGraphite(client) + lastId, _, err := srv.getLastUpdateGraphite(client) if err != nil { s5l.Printf("fwd-graphite: lastupdate returned err: %v", err) client.Disconnect() @@ -38,7 +38,7 @@ tryResync: nextBatch: for { - updates, err := self.getUpdatesAfterInvoke(lastId) + updates, err := srv.getUpdatesAfterInvoke(lastId) if err != nil { s5l.Printf("fwd-graphite: failed reading updates: %v\n", err) time.Sleep(500 * time.Millisecond) @@ -80,6 +80,6 @@ tryResync: } } -func (self Server) RunForwardingToGraphite(forwardHost string, basePath string) { - self.handleForwardingToGraphite(forwardHost, basePath) +func (srv Server) RunForwardingToGraphite(forwardHost string, basePath string) { + srv.handleForwardingToGraphite(forwardHost, basePath) } diff --git a/src/hub/src/spreadspace.org/sfive/s5srvForwardPiwik.go b/src/hub/src/spreadspace.org/sfive/s5srvForwardPiwik.go index 55e67e1..a7566a8 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvForwardPiwik.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvForwardPiwik.go @@ -17,10 +17,10 @@ type PiwikBulkRequest struct { TokenAuth string `json:"token_auth"` } -func (self Server) getLastUpdatePiwik(piwikURL, siteURL string, siteID uint, token string, client *http.Client) (latestId int, storeId string, err error) { +func (srv Server) getLastUpdatePiwik(piwikURL, siteURL string, siteID uint, token string, client *http.Client) (latestId int, storeId string, err error) { // TODO: ask piwik what the last update was... - latestId, err = 0, nil //self.getLastUpdateIdInvoke() + latestId, err = 0, nil //srv.getLastUpdateIdInvoke() if err != nil { s5l.Printf("fwd-piwik: failed to get own hubid: %v\n", err) return @@ -29,10 +29,10 @@ func (self Server) getLastUpdatePiwik(piwikURL, siteURL string, siteID uint, tok return } -func (self Server) handleForwardingToPiwik(piwikURL, siteURL string, siteID uint, token string, client *http.Client) { +func (srv Server) handleForwardingToPiwik(piwikURL, siteURL string, siteID uint, token string, client *http.Client) { tryResync: for { - lastId, _, err := self.getLastUpdatePiwik(piwikURL, siteURL, siteID, token, client) + lastId, _, err := srv.getLastUpdatePiwik(piwikURL, siteURL, siteID, token, client) if err != nil { s5l.Printf("fwd-piwik: lastupdate returned err: %v", err) time.Sleep(5 * time.Second) @@ -42,7 +42,7 @@ tryResync: nextBatch: for { - updates, err := self.getUpdatesAfterInvoke(lastId) + updates, err := srv.getUpdatesAfterInvoke(lastId) if err != nil { s5l.Printf("fwd-piwik: failed reading updates: %v\n", err) time.Sleep(500 * time.Millisecond) @@ -106,6 +106,6 @@ tryResync: } } -func (self Server) RunForwardingToPiwik(piwikURL, siteURL string, siteID uint, piwikToken string) { - self.handleForwardingToPiwik(piwikURL, siteURL, siteID, piwikToken, http.DefaultClient) +func (srv Server) RunForwardingToPiwik(piwikURL, siteURL string, siteID uint, piwikToken string) { + srv.handleForwardingToPiwik(piwikURL, siteURL, siteID, piwikToken, http.DefaultClient) } diff --git a/src/hub/src/spreadspace.org/sfive/s5srvPipe.go b/src/hub/src/spreadspace.org/sfive/s5srvPipe.go index 1a4d6a2..9744e38 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvPipe.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvPipe.go @@ -6,7 +6,7 @@ import ( "net" ) -func (self Server) handleConnection(conn net.Conn) { +func (srv Server) handleConnection(conn net.Conn) { reader := bufio.NewReader(conn) buffer, err := reader.ReadBytes('\n') if err != nil { @@ -38,11 +38,11 @@ func (self Server) handleConnection(conn net.Conn) { continue } - self.appendData <- value + srv.appendData <- value } } -func (self Server) handlePacketConn(pconn net.PacketConn) { +func (srv Server) handlePacketConn(pconn net.PacketConn) { decoder := NewPlainDecoder() buffer := make([]byte, 64*1024) for { @@ -54,14 +54,14 @@ func (self Server) handlePacketConn(pconn net.PacketConn) { data := buffer[0:n] value, err := decoder.Decode(data) if err == nil { - self.appendData <- value + srv.appendData <- value } else { s5l.Printf("p-pipe: failed to decode message: %v\n", err) } } } -func (self Server) ServePipe(pipePath string) { +func (srv Server) ServePipe(pipePath string) { ln, err := net.Listen("unix", pipePath) if err != nil { s5l.Printf("pipe: failed to connect: %v", err) @@ -76,11 +76,11 @@ func (self Server) ServePipe(pipePath string) { // ignore continue } - go self.handleConnection(conn) + go srv.handleConnection(conn) } } -func (self Server) ServeGramPipe(pipePath string) { +func (srv Server) ServeGramPipe(pipePath string) { pconn, err := net.ListenPacket("unixgram", pipePath) if err != nil { s5l.Printf("p-pipe: failed to listen: %v", err) @@ -88,5 +88,5 @@ func (self Server) ServeGramPipe(pipePath string) { } defer pconn.Close() - self.handlePacketConn(pconn) + srv.handlePacketConn(pconn) } diff --git a/src/hub/src/spreadspace.org/sfive/s5srvWeb.go b/src/hub/src/spreadspace.org/sfive/s5srvWeb.go index 6d3af91..6e2cf00 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvWeb.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvWeb.go @@ -12,14 +12,14 @@ import ( "github.com/zenazn/goji/web" ) -func (self Server) healthz(c web.C, w http.ResponseWriter, r *http.Request) { +func (srv Server) healthz(c web.C, w http.ResponseWriter, r *http.Request) { // TODO: do a more sophisticated check - fmt.Fprintf(w, "%s\n", self.store.GetStoreId()) + fmt.Fprintf(w, "%s\n", srv.store.GetStoreId()) } -func (self Server) getSourcesList(c web.C, w http.ResponseWriter, r *http.Request) { +func (srv Server) getSourcesList(c web.C, w http.ResponseWriter, r *http.Request) { const resourceName = "sources" - values, err := self.store.GetSources() + values, err := srv.store.GetSources() if err != nil { http.Error(w, fmt.Sprintf("failed to retrieve %s: %v", resourceName, err), http.StatusInternalServerError) return @@ -32,14 +32,14 @@ func (self Server) getSourcesList(c web.C, w http.ResponseWriter, r *http.Reques fmt.Fprintf(w, "%s", jsonString) } -func (self Server) getSource(c web.C, w http.ResponseWriter, r *http.Request) { +func (srv Server) getSource(c web.C, w http.ResponseWriter, r *http.Request) { const resourceName = "source" id, err := strconv.ParseInt(c.URLParams["id"], 10, 64) if err != nil { http.Error(w, fmt.Sprintf("invalid id: %s: %v", resourceName, err), http.StatusBadRequest) return } - value, err := self.store.GetSource(int(id)) + value, err := srv.store.GetSource(int(id)) if err != nil { if err == ErrNotFound { http.Error(w, fmt.Sprintf("failed to retrieve %s: %v", resourceName, err), http.StatusNotFound) @@ -56,9 +56,9 @@ func (self Server) getSource(c web.C, w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "%s", jsonString) } -func (self Server) getUpdateList(c web.C, w http.ResponseWriter, r *http.Request) { +func (srv Server) getUpdateList(c web.C, w http.ResponseWriter, r *http.Request) { const resourceName = "updates" - values, err := self.getUpdatesInvoke() + values, err := srv.getUpdatesInvoke() if err != nil { http.Error(w, fmt.Sprintf("failed to retrieve %s: %v", resourceName, err), http.StatusInternalServerError) return @@ -71,14 +71,14 @@ func (self Server) getUpdateList(c web.C, w http.ResponseWriter, r *http.Request fmt.Fprintf(w, "%s", jsonString) } -func (self Server) getUpdate(c web.C, w http.ResponseWriter, r *http.Request) { +func (srv Server) getUpdate(c web.C, w http.ResponseWriter, r *http.Request) { const resourceName = "update" id, err := strconv.ParseInt(c.URLParams["id"], 10, 64) if err != nil { http.Error(w, fmt.Sprintf("invalid id: %s: %v", resourceName, err), http.StatusBadRequest) return } - value, err := self.store.GetUpdate(int(id)) + value, err := srv.store.GetUpdate(int(id)) if err != nil { if err == ErrNotFound { http.Error(w, fmt.Sprintf("failed to retrieve %s: %v", resourceName, err), http.StatusNotFound) @@ -95,7 +95,7 @@ func (self Server) getUpdate(c web.C, w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "%s", jsonString) } -func (self Server) postUpdate(c web.C, w http.ResponseWriter, r *http.Request) { +func (srv Server) postUpdate(c web.C, w http.ResponseWriter, r *http.Request) { const resourceName = "update" decoder := NewPlainDecoder() @@ -113,7 +113,7 @@ func (self Server) postUpdate(c web.C, w http.ResponseWriter, r *http.Request) { data: container.Data, response: make(chan bool, 2)} defer close(token.response) - self.appendManyData <- token + srv.appendManyData <- token success := <-token.response if !success { http.Error(w, "failed to store data", http.StatusInternalServerError) @@ -129,13 +129,13 @@ func (self Server) postUpdate(c web.C, w http.ResponseWriter, r *http.Request) { return } - self.appendData <- data + srv.appendData <- data // TODO send response channel, wait for OK } -func (self Server) getLastUpdateId(c web.C, w http.ResponseWriter, r *http.Request) { +func (srv Server) getLastUpdateId(c web.C, w http.ResponseWriter, r *http.Request) { const resourceName = "lastupdateid" - value, err := self.store.GetLastUpdateId() + value, err := srv.store.GetLastUpdateId() if err != nil { http.Error(w, fmt.Sprintf("failed to retrieve %s: %v", resourceName, err), http.StatusInternalServerError) return @@ -143,10 +143,10 @@ func (self Server) getLastUpdateId(c web.C, w http.ResponseWriter, r *http.Reque fmt.Fprintf(w, "%d", value) } -func (self Server) getLastUpdateIdForUuid(c web.C, w http.ResponseWriter, r *http.Request) { +func (srv Server) getLastUpdateIdForUuid(c web.C, w http.ResponseWriter, r *http.Request) { const resourceName = "lastupdateid" id := c.URLParams["id"] - value, err := self.store.GetLastUpdateForUuid(id) + value, err := srv.store.GetLastUpdateForUuid(id) if err != nil { http.Error(w, fmt.Sprintf("failed to retrieve %s: %v", resourceName, err), http.StatusInternalServerError) return @@ -154,7 +154,7 @@ func (self Server) getLastUpdateIdForUuid(c web.C, w http.ResponseWriter, r *htt fmt.Fprintf(w, "%d", value) } -func (self Server) ServeWeb(vizAppLocation string) { +func (srv Server) ServeWeb(vizAppLocation string) { if _, err := os.Stat(vizAppLocation); err != nil { if os.IsNotExist(err) { s5l.Panicf("web: viz-app at %s does not exist.", vizAppLocation) @@ -163,14 +163,14 @@ func (self Server) ServeWeb(vizAppLocation string) { } } - goji.Get("/healthz", self.healthz) - goji.Get("/sources", self.getSourcesList) - goji.Get("/sources/:id", self.getSource) - goji.Get("/updates", self.getUpdateList) - goji.Get("/updates/:id", self.getUpdate) - goji.Post("/updates", self.postUpdate) - goji.Get("/lastupdate", self.getLastUpdateId) - goji.Get("/lastupdate/:id", self.getLastUpdateIdForUuid) + goji.Get("/healthz", srv.healthz) + goji.Get("/sources", srv.getSourcesList) + goji.Get("/sources/:id", srv.getSource) + goji.Get("/updates", srv.getUpdateList) + goji.Get("/updates/:id", srv.getUpdate) + goji.Post("/updates", srv.postUpdate) + goji.Get("/lastupdate", srv.getLastUpdateId) + goji.Get("/lastupdate/:id", srv.getLastUpdateIdForUuid) goji.Handle("/viz/*", http.StripPrefix("/viz/", http.FileServer(http.Dir(vizAppLocation)))) goji.Serve() diff --git a/src/hub/src/spreadspace.org/sfive/s5typesApi.go b/src/hub/src/spreadspace.org/sfive/s5typesApi.go index ae99b03..5ac368f 100644 --- a/src/hub/src/spreadspace.org/sfive/s5typesApi.go +++ b/src/hub/src/spreadspace.org/sfive/s5typesApi.go @@ -55,15 +55,15 @@ type DataUpdateFullContainer struct { Data []DataUpdateFull `json:"data"` } -func (self *DataUpdateFull) CopyFromSourceId(id *SourceId) { - self.Hostname = id.Hostname - self.StreamId = id.StreamId - self.Tags = id.Tags - self.Version = id.Version +func (duf *DataUpdateFull) CopyFromSourceId(src *SourceId) { + duf.Hostname = src.Hostname + duf.StreamId = src.StreamId + duf.Tags = src.Tags + duf.Version = src.Version } -func (self *DataUpdateFull) CopyFromUpdate(id *DataUpdate) { - self.StartTime = id.StartTime - self.Duration = id.Duration - self.Data = id.Data +func (duf *DataUpdateFull) CopyFromUpdate(du *DataUpdate) { + duf.StartTime = du.StartTime + duf.Duration = du.Duration + duf.Data = du.Data } -- cgit v1.2.3 From 7e2fc6b27d3cc278e20e19e87cb1869b9edc5c77 Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Thu, 27 Apr 2017 05:38:15 +0200 Subject: get updates limit --- src/hub/src/spreadspace.org/sfive/s5log.go | 4 +-- src/hub/src/spreadspace.org/sfive/s5srv.go | 27 ++++-------------- src/hub/src/spreadspace.org/sfive/s5srvForward.go | 2 +- .../src/spreadspace.org/sfive/s5srvForwardEs.go | 9 ++---- .../spreadspace.org/sfive/s5srvForwardGraphite.go | 2 +- .../src/spreadspace.org/sfive/s5srvForwardPiwik.go | 2 +- src/hub/src/spreadspace.org/sfive/s5srvWeb.go | 2 +- src/hub/src/spreadspace.org/sfive/s5store.go | 33 +++++++++++++--------- src/hub/src/spreadspace.org/sfive/s5store_test.go | 4 +-- 9 files changed, 35 insertions(+), 50 deletions(-) (limited to 'src') diff --git a/src/hub/src/spreadspace.org/sfive/s5log.go b/src/hub/src/spreadspace.org/sfive/s5log.go index 9b80f6f..429a537 100644 --- a/src/hub/src/spreadspace.org/sfive/s5log.go +++ b/src/hub/src/spreadspace.org/sfive/s5log.go @@ -9,6 +9,6 @@ import ( var ( s5l = log.New(os.Stderr, "[s5]\t", log.LstdFlags) // use ioutil.Discard to switch that thing off - // s5tl = log.New(os.Stderr, "[s5dbg]\t", log.LstdFlags) - s5tl = log.New(ioutil.Discard, "[s5dbg]\t", log.LstdFlags) + // s5dl = log.New(os.Stderr, "[s5dbg]\t", log.LstdFlags) + s5dl = log.New(ioutil.Discard, "[s5dbg]\t", log.LstdFlags) ) diff --git a/src/hub/src/spreadspace.org/sfive/s5srv.go b/src/hub/src/spreadspace.org/sfive/s5srv.go index ac3e086..26eb0ac 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srv.go +++ b/src/hub/src/spreadspace.org/sfive/s5srv.go @@ -16,10 +16,7 @@ type getUpdatesResult struct { type getUpdatesAfterToken struct { id int - response chan getUpdatesResult -} - -type getUpdatesToken struct { + limit int response chan getUpdatesResult } @@ -45,9 +42,8 @@ type Server struct { quit chan bool done chan bool appendData chan DataUpdateFull - appendManyData chan appendManyToken // chan []DataUpdateFull + appendManyData chan appendManyToken getUpdatesAfterChan chan getUpdatesAfterToken - getUpdatesChan chan getUpdatesToken getHubIdChan chan getHubIdToken getLastUpdateIdChan chan getLastUpdateIdToken } @@ -80,10 +76,7 @@ func (srv Server) appendActor() { token.response <- true } case token := <-srv.getUpdatesAfterChan: - values, err := srv.store.GetUpdatesAfter(token.id) - token.response <- getUpdatesResult{values, err} - case token := <-srv.getUpdatesChan: - values, err := srv.store.GetUpdates() + values, err := srv.store.GetUpdatesAfter(token.id, token.limit) token.response <- getUpdatesResult{values, err} case token := <-srv.getHubIdChan: token.response <- getHubIdResult{srv.store.GetStoreId()} @@ -94,22 +87,14 @@ func (srv Server) appendActor() { } } -func (srv Server) getUpdatesAfterInvoke(id int) ([]DataUpdateFull, error) { - token := getUpdatesAfterToken{id: id, response: make(chan getUpdatesResult, 1)} +func (srv Server) getUpdatesAfterInvoke(id, limit int) ([]DataUpdateFull, error) { + token := getUpdatesAfterToken{id: id, limit: limit, response: make(chan getUpdatesResult, 1)} defer close(token.response) srv.getUpdatesAfterChan <- token res := <-token.response return res.values, res.err } -func (srv Server) getUpdatesInvoke() ([]DataUpdateFull, error) { - token := getUpdatesToken{response: make(chan getUpdatesResult, 1)} - defer close(token.response) - srv.getUpdatesChan <- token - res := <-token.response - return res.values, res.err -} - func (srv Server) getHubIdInvoke() string { token := getHubIdToken{response: make(chan getHubIdResult, 1)} defer close(token.response) @@ -134,7 +119,6 @@ func (srv Server) Close() { close(srv.appendData) close(srv.appendManyData) close(srv.getUpdatesAfterChan) - close(srv.getUpdatesChan) close(srv.getHubIdChan) close(srv.getLastUpdateIdChan) srv.store.Close() @@ -153,7 +137,6 @@ func NewServer(dbPath string) (server *Server, err error) { server.appendData = make(chan DataUpdateFull, 5) server.appendManyData = make(chan appendManyToken, 5) server.getUpdatesAfterChan = make(chan getUpdatesAfterToken, 1) - server.getUpdatesChan = make(chan getUpdatesToken, 3) server.getHubIdChan = make(chan getHubIdToken, 1) server.getLastUpdateIdChan = make(chan getLastUpdateIdToken, 1) go server.appendActor() diff --git a/src/hub/src/spreadspace.org/sfive/s5srvForward.go b/src/hub/src/spreadspace.org/sfive/s5srvForward.go index 6c9823f..d5fee0c 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvForward.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvForward.go @@ -74,7 +74,7 @@ tryResync: nextBatch: for { - updates, err := srv.getUpdatesAfterInvoke(lastId) + updates, err := srv.getUpdatesAfterInvoke(lastId, 5000) if err != nil { s5l.Printf("fwd: failed reading updates: %v\n", err) time.Sleep(500 * time.Millisecond) diff --git a/src/hub/src/spreadspace.org/sfive/s5srvForwardEs.go b/src/hub/src/spreadspace.org/sfive/s5srvForwardEs.go index 622b307..56af7ca 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvForwardEs.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvForwardEs.go @@ -21,7 +21,7 @@ func (srv Server) getLastUpdateEs(baseurl string, client *http.Client) (latestId storeId = srv.getHubIdInvoke() queryJson := fmt.Sprintf(lastUpdateJson, storeId) - s5tl.Printf("fwd-es: query: %s", queryJson) + s5dl.Printf("fwd-es: query: %s", queryJson) var resp *http.Response resp, err = client.Post(url, "application/json", strings.NewReader(queryJson)) @@ -43,7 +43,7 @@ func (srv Server) getLastUpdateEs(baseurl string, client *http.Client) (latestId return } - s5tl.Printf("fwd-es: lastupdate response: %s\n", body) + s5dl.Printf("fwd-es: lastupdate response: %s\n", body) if len(body) == 0 { latestId = -1 @@ -82,7 +82,7 @@ tryResync: nextBatch: for { - updates, err := srv.getUpdatesAfterInvoke(lastId) + updates, err := srv.getUpdatesAfterInvoke(lastId, 5000) if err != nil { s5l.Printf("fwd-es: failed reading updates: %v\n", err) time.Sleep(500 * time.Millisecond) @@ -109,8 +109,6 @@ tryResync: postData.WriteRune('\n') } - //s5tl.Printf("fwd-es: marshalled:\n%v\n", (string)(postData.Bytes())) - s5l.Printf("fwd-es: marshal OK") resp, err := client.Post(url, "application/json", bytes.NewReader(postData.Bytes())) @@ -131,7 +129,6 @@ tryResync: s5l.Printf("fwd-es: all posts OK") lastId = findMaxId(updates) s5l.Printf("fwd-es: new lastid: %d", lastId) - //time.Sleep(1 * time.Second) } } } diff --git a/src/hub/src/spreadspace.org/sfive/s5srvForwardGraphite.go b/src/hub/src/spreadspace.org/sfive/s5srvForwardGraphite.go index 8ab0ac6..d865dac 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvForwardGraphite.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvForwardGraphite.go @@ -38,7 +38,7 @@ tryResync: nextBatch: for { - updates, err := srv.getUpdatesAfterInvoke(lastId) + updates, err := srv.getUpdatesAfterInvoke(lastId, 5000) if err != nil { s5l.Printf("fwd-graphite: failed reading updates: %v\n", err) time.Sleep(500 * time.Millisecond) diff --git a/src/hub/src/spreadspace.org/sfive/s5srvForwardPiwik.go b/src/hub/src/spreadspace.org/sfive/s5srvForwardPiwik.go index a7566a8..6cae87e 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvForwardPiwik.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvForwardPiwik.go @@ -42,7 +42,7 @@ tryResync: nextBatch: for { - updates, err := srv.getUpdatesAfterInvoke(lastId) + updates, err := srv.getUpdatesAfterInvoke(lastId, 5000) if err != nil { s5l.Printf("fwd-piwik: failed reading updates: %v\n", err) time.Sleep(500 * time.Millisecond) diff --git a/src/hub/src/spreadspace.org/sfive/s5srvWeb.go b/src/hub/src/spreadspace.org/sfive/s5srvWeb.go index 6e2cf00..12e6ccb 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvWeb.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvWeb.go @@ -58,7 +58,7 @@ func (srv Server) getSource(c web.C, w http.ResponseWriter, r *http.Request) { func (srv Server) getUpdateList(c web.C, w http.ResponseWriter, r *http.Request) { const resourceName = "updates" - values, err := srv.getUpdatesInvoke() + values, err := srv.getUpdatesAfterInvoke(-1, 3) // TODO: get start and limit from param if err != nil { http.Error(w, fmt.Sprintf("failed to retrieve %s: %v", resourceName, err), http.StatusInternalServerError) return diff --git a/src/hub/src/spreadspace.org/sfive/s5store.go b/src/hub/src/spreadspace.org/sfive/s5store.go index 64cb879..123fcd7 100644 --- a/src/hub/src/spreadspace.org/sfive/s5store.go +++ b/src/hub/src/spreadspace.org/sfive/s5store.go @@ -8,6 +8,10 @@ import ( "github.com/pborman/uuid" ) +const ( + StoreGetUpdatesLimit = 42000 +) + type Store struct { hubId string db *bolt.DB @@ -256,7 +260,7 @@ func (s Store) getClientsByUpdateId(tx *bolt.Tx, id int) (res []ClientData, err return } -func (s Store) CreateDataUpdateFullFromDb(tx *bolt.Tx, duId int, du dataUpdateDb) (res DataUpdateFull, err error) { +func (s Store) createDataUpdateFullFromDb(tx *bolt.Tx, duId int, du dataUpdateDb) (res DataUpdateFull, err error) { var clients []ClientData if clients, err = s.getClientsByUpdateId(tx, duId); err != nil { return @@ -276,10 +280,15 @@ func (s Store) CreateDataUpdateFullFromDb(tx *bolt.Tx, duId int, du dataUpdateDb return } -func (s Store) GetUpdatesAfter(id int) (res []DataUpdateFull, err error) { - if id < 0 { +func (s Store) GetUpdatesAfter(id, limit int) (res []DataUpdateFull, err error) { + res = []DataUpdateFull{} + if id < 0 { // TODO: interpret negative values as last x values id = 0 } + if limit < 0 || limit > StoreGetUpdatesLimit { + s5l.Printf("store: truncating get-update limit to %d (from %d)", StoreGetUpdatesLimit, limit) + limit = StoreGetUpdatesLimit + } err = s.db.View(func(tx *bolt.Tx) error { c := tx.Bucket([]byte(dataUpdatesBn)).Cursor() if k, _ := c.Seek(itob(id)); k == nil { @@ -292,22 +301,20 @@ func (s Store) GetUpdatesAfter(id int) (res []DataUpdateFull, err error) { } var duf DataUpdateFull - duf, err := s.CreateDataUpdateFullFromDb(tx, btoi(k), d) + duf, err := s.createDataUpdateFullFromDb(tx, btoi(k), d) if err != nil { return err } res = append(res, duf) + if len(res) >= limit { + return nil + } } return nil }) return } -func (s Store) GetUpdates() (res []DataUpdateFull, err error) { - // TODO: implement from:to with limit - return s.GetUpdatesAfter(-1) -} - func (s Store) GetUpdate(id int) (res DataUpdateFull, err error) { err = s.db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(dataUpdatesBn)) @@ -323,7 +330,7 @@ func (s Store) GetUpdate(id int) (res DataUpdateFull, err error) { } var err error - if res, err = s.CreateDataUpdateFullFromDb(tx, id, d); err != nil { + if res, err = s.createDataUpdateFullFromDb(tx, id, d); err != nil { return err } return nil @@ -331,10 +338,6 @@ func (s Store) GetUpdate(id int) (res DataUpdateFull, err error) { return } -type lastUpdateQueryResult struct { - MaxDataUpdateId *int -} - func (s Store) GetLastUpdateForUuid(uuid string) (updateId int, err error) { // TODO: implement me! updateId = 0 @@ -358,9 +361,11 @@ func NewStore(dbPath string) (Store, error) { if err != nil { return Store{}, err } + s5l.Printf("store: initialized (UUID: %s)", hubid) return Store{hubid, db}, nil } func (s Store) Close() { + s5l.Printf("store: closing") s.db.Close() } diff --git a/src/hub/src/spreadspace.org/sfive/s5store_test.go b/src/hub/src/spreadspace.org/sfive/s5store_test.go index 7b45e59..e5f0a12 100644 --- a/src/hub/src/spreadspace.org/sfive/s5store_test.go +++ b/src/hub/src/spreadspace.org/sfive/s5store_test.go @@ -64,7 +64,7 @@ func TestGetUpdatesAfter(t *testing.T) { return } - res, err := store.GetUpdatesAfter(2) + res, err := store.GetUpdatesAfter(2, -1) t.Logf("got updates (err %v):\n%#v", err, res) } @@ -151,7 +151,7 @@ func BenchmarkGetUpdatesAfter(b *testing.B) { latestId := -1 for { - updates, err := store.GetUpdatesAfter(latestId) + updates, err := store.GetUpdatesAfter(latestId, -1) if err != nil { b.Errorf("Failed to retrieve: %v", err) } -- cgit v1.2.3 From 44012412628618c64081fc5ea6d07719439ac270 Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Thu, 27 Apr 2017 05:45:54 +0200 Subject: disable sleep during forward --- src/hub/src/spreadspace.org/sfive/s5srvForward.go | 1 - src/hub/src/spreadspace.org/sfive/s5srvForwardGraphite.go | 1 - src/hub/src/spreadspace.org/sfive/s5srvForwardPiwik.go | 1 - 3 files changed, 3 deletions(-) (limited to 'src') diff --git a/src/hub/src/spreadspace.org/sfive/s5srvForward.go b/src/hub/src/spreadspace.org/sfive/s5srvForward.go index d5fee0c..8170165 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvForward.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvForward.go @@ -111,7 +111,6 @@ tryResync: s5l.Printf("fwd: post OK") lastId = findMaxId(updates) s5l.Printf("fwd: new lastid: %d", lastId) - time.Sleep(1 * time.Second) } } } diff --git a/src/hub/src/spreadspace.org/sfive/s5srvForwardGraphite.go b/src/hub/src/spreadspace.org/sfive/s5srvForwardGraphite.go index d865dac..2144e6a 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvForwardGraphite.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvForwardGraphite.go @@ -75,7 +75,6 @@ tryResync: s5l.Printf("fwd-graphite: all metrics sent") lastId = findMaxId(updates) s5l.Printf("fwd-graphite: new lastid: %d", lastId) - //time.Sleep(1 * time.Second) } } } diff --git a/src/hub/src/spreadspace.org/sfive/s5srvForwardPiwik.go b/src/hub/src/spreadspace.org/sfive/s5srvForwardPiwik.go index 6cae87e..236a474 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvForwardPiwik.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvForwardPiwik.go @@ -101,7 +101,6 @@ tryResync: s5l.Printf("fwd-piwik: all posts OK") lastId = findMaxId(updates) s5l.Printf("fwd-piwik: new lastid: %d", lastId) - //time.Sleep(1 * time.Second) } } } -- cgit v1.2.3 From 86773a81c7e609cb04c77da71175247a7ef2ae3a Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Thu, 27 Apr 2017 06:04:45 +0200 Subject: remove vizualization minor name refactoring --- src/hub/src/spreadspace.org/sfive-hub/s5hub.go | 31 +++-- src/hub/src/spreadspace.org/sfive/s5srv.go | 7 +- src/hub/src/spreadspace.org/sfive/s5srvPipe.go | 18 ++- src/hub/src/spreadspace.org/sfive/s5srvWeb.go | 45 +++---- src/hub/test-srv | 2 +- src/viz/README | 4 - src/viz/dygraph-combined.js | 2 - src/viz/index.css | 180 ------------------------- src/viz/index.html | 19 --- src/viz/index.js | 173 ------------------------ src/viz/jquery-2.1.1.min.js | 4 - src/viz/stats.html | 82 ----------- src/viz/updates | 36 ----- 13 files changed, 46 insertions(+), 557 deletions(-) delete mode 100644 src/viz/README delete mode 100644 src/viz/dygraph-combined.js delete mode 100644 src/viz/index.css delete mode 100644 src/viz/index.html delete mode 100644 src/viz/index.js delete mode 100644 src/viz/jquery-2.1.1.min.js delete mode 100644 src/viz/stats.html delete mode 100644 src/viz/updates (limited to 'src') diff --git a/src/hub/src/spreadspace.org/sfive-hub/s5hub.go b/src/hub/src/spreadspace.org/sfive-hub/s5hub.go index 9589812..6c6463b 100644 --- a/src/hub/src/spreadspace.org/sfive-hub/s5hub.go +++ b/src/hub/src/spreadspace.org/sfive-hub/s5hub.go @@ -26,7 +26,6 @@ func main() { piwikSiteURL := flag.String("piwik-site-url", "", "use this base url for the site") piwikSiteID := flag.Uint("piwik-site-id", 1, "use this site-id for piwik") piwikToken := flag.String("piwik-token", "", "the auth token for piwik") - vizAppDir := flag.String("viz-dir", "/usr/share/sfive/viz", "base-path to the viz application") help := flag.Bool("help", false, "show usage") flag.Parse() @@ -37,11 +36,11 @@ func main() { return } - server, err := sfive.NewServer(*db) + srv, err := sfive.NewServer(*db) if err != nil { s5hl.Fatalf("failed to initialize: %v", err) } - defer server.Close() + defer srv.Close() var wg sync.WaitGroup @@ -50,7 +49,7 @@ func main() { go func() { defer wg.Done() s5hl.Printf("start pipe at %v\n", *pipe) - server.ServePipe(*pipe) + srv.ServePipe(*pipe) s5hl.Println("pipe finished") }() } @@ -59,8 +58,8 @@ func main() { wg.Add(1) go func() { defer wg.Done() - s5hl.Printf("start pipegram at %v\n", *ppipe) - server.ServeGramPipe(*ppipe) + s5hl.Printf("starting pipegram at %v\n", *ppipe) + srv.ServePipegram(*ppipe) s5hl.Println("pipegram finished") }() } @@ -69,8 +68,8 @@ func main() { wg.Add(1) go func() { defer wg.Done() - s5hl.Println("start web-srv") - server.ServeWeb(*vizAppDir) + s5hl.Println("starting web") + srv.ServeWeb() s5hl.Println("web finished") }() } @@ -79,8 +78,8 @@ func main() { wg.Add(1) go func() { defer wg.Done() - s5hl.Println("start forward") - server.RunForwarding(*forward) + s5hl.Println("starting forward") + srv.RunForwarding(*forward) s5hl.Println("forward finished") }() } @@ -89,8 +88,8 @@ func main() { wg.Add(1) go func() { defer wg.Done() - s5hl.Println("start elastic-search forward") - server.RunForwardingToElasticSearch(*forwardES) + s5hl.Println("starting elastic-search forward") + srv.RunForwardingToElasticSearch(*forwardES) s5hl.Println("elastic-search forward finished") }() } @@ -99,8 +98,8 @@ func main() { wg.Add(1) go func() { defer wg.Done() - s5hl.Println("start graphite forward") - server.RunForwardingToGraphite(*forwardGraphite, *graphiteBasePath) + s5hl.Println("starting graphite forward") + srv.RunForwardingToGraphite(*forwardGraphite, *graphiteBasePath) s5hl.Println("graphite forward finished") }() } @@ -109,8 +108,8 @@ func main() { wg.Add(1) go func() { defer wg.Done() - s5hl.Println("start piwik forward") - server.RunForwardingToPiwik(*forwardPiwik, *piwikSiteURL, *piwikSiteID, *piwikToken) + s5hl.Println("starting piwik forward") + srv.RunForwardingToPiwik(*forwardPiwik, *piwikSiteURL, *piwikSiteID, *piwikToken) s5hl.Println("piwik forward finished") }() } diff --git a/src/hub/src/spreadspace.org/sfive/s5srv.go b/src/hub/src/spreadspace.org/sfive/s5srv.go index 26eb0ac..c197dad 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srv.go +++ b/src/hub/src/spreadspace.org/sfive/s5srv.go @@ -65,12 +65,12 @@ func (srv Server) appendActor() { } } if err != nil { - s5l.Printf("failed to store data: %v\n", err) + s5l.Printf("server: failed to store data: %v\n", err) } case token := <-srv.appendManyData: err := srv.store.AppendMany(token.data) if err != nil { - s5l.Printf("failed to store many data: %v\n", err) + s5l.Printf("server: failed to store many data: %v\n", err) token.response <- false } else { token.response <- true @@ -112,6 +112,7 @@ func (srv Server) getLastUpdateIdInvoke() (int, error) { } func (srv Server) Close() { + s5l.Printf("server: shutting down\n") srv.quit <- true <-srv.done close(srv.quit) @@ -122,6 +123,7 @@ func (srv Server) Close() { close(srv.getHubIdChan) close(srv.getLastUpdateIdChan) srv.store.Close() + s5l.Printf("server: finished\n") } func NewServer(dbPath string) (server *Server, err error) { @@ -140,5 +142,6 @@ func NewServer(dbPath string) (server *Server, err error) { server.getHubIdChan = make(chan getHubIdToken, 1) server.getLastUpdateIdChan = make(chan getLastUpdateIdToken, 1) go server.appendActor() + s5l.Printf("server: initialized\n") return } diff --git a/src/hub/src/spreadspace.org/sfive/s5srvPipe.go b/src/hub/src/spreadspace.org/sfive/s5srvPipe.go index 9744e38..95904a7 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvPipe.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvPipe.go @@ -6,7 +6,7 @@ import ( "net" ) -func (srv Server) handleConnection(conn net.Conn) { +func (srv Server) pipeHandle(conn net.Conn) { reader := bufio.NewReader(conn) buffer, err := reader.ReadBytes('\n') if err != nil { @@ -30,8 +30,6 @@ func (srv Server) handleConnection(conn net.Conn) { return } - // s5l.Printf("msg: %v", string(buffer)) - value, err := marshaller.Decode(buffer) if err != nil { s5l.Printf("pipe: failed to decode message: %v\n", err) @@ -42,13 +40,13 @@ func (srv Server) handleConnection(conn net.Conn) { } } -func (srv Server) handlePacketConn(pconn net.PacketConn) { +func (srv Server) pipegramHandle(pconn net.PacketConn) { decoder := NewPlainDecoder() buffer := make([]byte, 64*1024) for { n, _, err := pconn.ReadFrom(buffer) if err != nil { - s5l.Printf("p-pipe: failed read: %v", err) + s5l.Printf("pipegram: failed read: %v", err) continue } data := buffer[0:n] @@ -56,7 +54,7 @@ func (srv Server) handlePacketConn(pconn net.PacketConn) { if err == nil { srv.appendData <- value } else { - s5l.Printf("p-pipe: failed to decode message: %v\n", err) + s5l.Printf("pipegram: failed to decode message: %v\n", err) } } } @@ -76,17 +74,17 @@ func (srv Server) ServePipe(pipePath string) { // ignore continue } - go srv.handleConnection(conn) + go srv.pipeHandle(conn) } } -func (srv Server) ServeGramPipe(pipePath string) { +func (srv Server) ServePipegram(pipePath string) { pconn, err := net.ListenPacket("unixgram", pipePath) if err != nil { - s5l.Printf("p-pipe: failed to listen: %v", err) + s5l.Printf("pipegram: failed to listen: %v", err) return } defer pconn.Close() - srv.handlePacketConn(pconn) + srv.pipegramHandle(pconn) } diff --git a/src/hub/src/spreadspace.org/sfive/s5srvWeb.go b/src/hub/src/spreadspace.org/sfive/s5srvWeb.go index 12e6ccb..0aad5ba 100644 --- a/src/hub/src/spreadspace.org/sfive/s5srvWeb.go +++ b/src/hub/src/spreadspace.org/sfive/s5srvWeb.go @@ -5,19 +5,18 @@ import ( "fmt" "io/ioutil" "net/http" - "os" "strconv" "github.com/zenazn/goji" "github.com/zenazn/goji/web" ) -func (srv Server) healthz(c web.C, w http.ResponseWriter, r *http.Request) { +func (srv Server) webHealthz(c web.C, w http.ResponseWriter, r *http.Request) { // TODO: do a more sophisticated check fmt.Fprintf(w, "%s\n", srv.store.GetStoreId()) } -func (srv Server) getSourcesList(c web.C, w http.ResponseWriter, r *http.Request) { +func (srv Server) webGetSourcesList(c web.C, w http.ResponseWriter, r *http.Request) { const resourceName = "sources" values, err := srv.store.GetSources() if err != nil { @@ -32,7 +31,7 @@ func (srv Server) getSourcesList(c web.C, w http.ResponseWriter, r *http.Request fmt.Fprintf(w, "%s", jsonString) } -func (srv Server) getSource(c web.C, w http.ResponseWriter, r *http.Request) { +func (srv Server) webGetSource(c web.C, w http.ResponseWriter, r *http.Request) { const resourceName = "source" id, err := strconv.ParseInt(c.URLParams["id"], 10, 64) if err != nil { @@ -56,7 +55,7 @@ func (srv Server) getSource(c web.C, w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "%s", jsonString) } -func (srv Server) getUpdateList(c web.C, w http.ResponseWriter, r *http.Request) { +func (srv Server) webGetUpdateList(c web.C, w http.ResponseWriter, r *http.Request) { const resourceName = "updates" values, err := srv.getUpdatesAfterInvoke(-1, 3) // TODO: get start and limit from param if err != nil { @@ -71,7 +70,7 @@ func (srv Server) getUpdateList(c web.C, w http.ResponseWriter, r *http.Request) fmt.Fprintf(w, "%s", jsonString) } -func (srv Server) getUpdate(c web.C, w http.ResponseWriter, r *http.Request) { +func (srv Server) webGetUpdate(c web.C, w http.ResponseWriter, r *http.Request) { const resourceName = "update" id, err := strconv.ParseInt(c.URLParams["id"], 10, 64) if err != nil { @@ -95,7 +94,7 @@ func (srv Server) getUpdate(c web.C, w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "%s", jsonString) } -func (srv Server) postUpdate(c web.C, w http.ResponseWriter, r *http.Request) { +func (srv Server) webPostUpdate(c web.C, w http.ResponseWriter, r *http.Request) { const resourceName = "update" decoder := NewPlainDecoder() @@ -133,7 +132,7 @@ func (srv Server) postUpdate(c web.C, w http.ResponseWriter, r *http.Request) { // TODO send response channel, wait for OK } -func (srv Server) getLastUpdateId(c web.C, w http.ResponseWriter, r *http.Request) { +func (srv Server) webGetLastUpdateId(c web.C, w http.ResponseWriter, r *http.Request) { const resourceName = "lastupdateid" value, err := srv.store.GetLastUpdateId() if err != nil { @@ -143,7 +142,7 @@ func (srv Server) getLastUpdateId(c web.C, w http.ResponseWriter, r *http.Reques fmt.Fprintf(w, "%d", value) } -func (srv Server) getLastUpdateIdForUuid(c web.C, w http.ResponseWriter, r *http.Request) { +func (srv Server) webGetLastUpdateIdForUuid(c web.C, w http.ResponseWriter, r *http.Request) { const resourceName = "lastupdateid" id := c.URLParams["id"] value, err := srv.store.GetLastUpdateForUuid(id) @@ -154,24 +153,14 @@ func (srv Server) getLastUpdateIdForUuid(c web.C, w http.ResponseWriter, r *http fmt.Fprintf(w, "%d", value) } -func (srv Server) ServeWeb(vizAppLocation string) { - if _, err := os.Stat(vizAppLocation); err != nil { - if os.IsNotExist(err) { - s5l.Panicf("web: viz-app at %s does not exist.", vizAppLocation) - } else { - s5l.Printf("web: failed to stat %s: %v", vizAppLocation, err) - } - } - - goji.Get("/healthz", srv.healthz) - goji.Get("/sources", srv.getSourcesList) - goji.Get("/sources/:id", srv.getSource) - goji.Get("/updates", srv.getUpdateList) - goji.Get("/updates/:id", srv.getUpdate) - goji.Post("/updates", srv.postUpdate) - goji.Get("/lastupdate", srv.getLastUpdateId) - goji.Get("/lastupdate/:id", srv.getLastUpdateIdForUuid) - goji.Handle("/viz/*", http.StripPrefix("/viz/", http.FileServer(http.Dir(vizAppLocation)))) - +func (srv Server) ServeWeb() { + goji.Get("/healthz", srv.webHealthz) + goji.Get("/sources", srv.webGetSourcesList) + goji.Get("/sources/:id", srv.webGetSource) + goji.Get("/updates", srv.webGetUpdateList) + goji.Get("/updates/:id", srv.webGetUpdate) + goji.Post("/updates", srv.webPostUpdate) + goji.Get("/lastupdate", srv.webGetLastUpdateId) + goji.Get("/lastupdate/:id", srv.webGetLastUpdateIdForUuid) goji.Serve() } diff --git a/src/hub/test-srv b/src/hub/test-srv index 803ac37..19a6539 100755 --- a/src/hub/test-srv +++ b/src/hub/test-srv @@ -5,4 +5,4 @@ TEST_DB="$TEST_D/db.bolt" mkdir -p "$TEST_D" rm -f "$TEST_D/pipe" "$TEST_D/pipegram" -exec ./bin/sfive-hub -db "$TEST_DB" -start-pipe-server -pipe "$TEST_D/pipe" -start-pipegram-server -pipegram "$TEST_D/pipegram" -start-web-server -viz-dir "$(pwd)/../viz" -bind=":8000" +exec ./bin/sfive-hub -db "$TEST_DB" -start-pipe-server -pipe "$TEST_D/pipe" -start-pipegram-server -pipegram "$TEST_D/pipegram" -start-web-server -bind=":8000" diff --git a/src/viz/README b/src/viz/README deleted file mode 100644 index 6e4d40c..0000000 --- a/src/viz/README +++ /dev/null @@ -1,4 +0,0 @@ -Data Vizualisation -================== - -A single page web application using data form HUB and showing it nicly. \ No newline at end of file diff --git a/src/viz/dygraph-combined.js b/src/viz/dygraph-combined.js deleted file mode 100644 index ad25e11..0000000 --- a/src/viz/dygraph-combined.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! @license Copyright 2011 Dan Vanderkam (danvdk@gmail.com) MIT-licensed (http://opensource.org/licenses/MIT) */ -Date.ext={};Date.ext.util={};Date.ext.util.xPad=function(a,c,b){if(typeof(b)=="undefined"){b=10}for(;parseInt(a,10)1;b/=10){a=c.toString()+a}return a.toString()};Date.prototype.locale="en-GB";if(document.getElementsByTagName("html")&&document.getElementsByTagName("html")[0].lang){Date.prototype.locale=document.getElementsByTagName("html")[0].lang}Date.ext.locales={};Date.ext.locales.en={a:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],A:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],b:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],B:["January","February","March","April","May","June","July","August","September","October","November","December"],c:"%a %d %b %Y %T %Z",p:["AM","PM"],P:["am","pm"],x:"%d/%m/%y",X:"%T"};Date.ext.locales["en-US"]=Date.ext.locales.en;Date.ext.locales["en-US"].c="%a %d %b %Y %r %Z";Date.ext.locales["en-US"].x="%D";Date.ext.locales["en-US"].X="%r";Date.ext.locales["en-GB"]=Date.ext.locales.en;Date.ext.locales["en-AU"]=Date.ext.locales["en-GB"];Date.ext.formats={a:function(a){return Date.ext.locales[a.locale].a[a.getDay()]},A:function(a){return Date.ext.locales[a.locale].A[a.getDay()]},b:function(a){return Date.ext.locales[a.locale].b[a.getMonth()]},B:function(a){return Date.ext.locales[a.locale].B[a.getMonth()]},c:"toLocaleString",C:function(a){return Date.ext.util.xPad(parseInt(a.getFullYear()/100,10),0)},d:["getDate","0"],e:["getDate"," "],g:function(a){return Date.ext.util.xPad(parseInt(Date.ext.util.G(a)/100,10),0)},G:function(c){var e=c.getFullYear();var b=parseInt(Date.ext.formats.V(c),10);var a=parseInt(Date.ext.formats.W(c),10);if(a>b){e++}else{if(a===0&&b>=52){e--}}return e},H:["getHours","0"],I:function(b){var a=b.getHours()%12;return Date.ext.util.xPad(a===0?12:a,0)},j:function(c){var a=c-new Date(""+c.getFullYear()+"/1/1 GMT");a+=c.getTimezoneOffset()*60000;var b=parseInt(a/60000/60/24,10)+1;return Date.ext.util.xPad(b,0,100)},m:function(a){return Date.ext.util.xPad(a.getMonth()+1,0)},M:["getMinutes","0"],p:function(a){return Date.ext.locales[a.locale].p[a.getHours()>=12?1:0]},P:function(a){return Date.ext.locales[a.locale].P[a.getHours()>=12?1:0]},S:["getSeconds","0"],u:function(a){var b=a.getDay();return b===0?7:b},U:function(e){var a=parseInt(Date.ext.formats.j(e),10);var c=6-e.getDay();var b=parseInt((a+c)/7,10);return Date.ext.util.xPad(b,0)},V:function(e){var c=parseInt(Date.ext.formats.W(e),10);var a=(new Date(""+e.getFullYear()+"/1/1")).getDay();var b=c+(a>4||a<=1?0:1);if(b==53&&(new Date(""+e.getFullYear()+"/12/31")).getDay()<4){b=1}else{if(b===0){b=Date.ext.formats.V(new Date(""+(e.getFullYear()-1)+"/12/31"))}}return Date.ext.util.xPad(b,0)},w:"getDay",W:function(e){var a=parseInt(Date.ext.formats.j(e),10);var c=7-Date.ext.formats.u(e);var b=parseInt((a+c)/7,10);return Date.ext.util.xPad(b,0,10)},y:function(a){return Date.ext.util.xPad(a.getFullYear()%100,0)},Y:"getFullYear",z:function(c){var b=c.getTimezoneOffset();var a=Date.ext.util.xPad(parseInt(Math.abs(b/60),10),0);var e=Date.ext.util.xPad(b%60,0);return(b>0?"-":"+")+a+e},Z:function(a){return a.toString().replace(/^.*\(([^)]+)\)$/,"$1")},"%":function(a){return"%"}};Date.ext.aggregates={c:"locale",D:"%m/%d/%y",h:"%b",n:"\n",r:"%I:%M:%S %p",R:"%H:%M",t:"\t",T:"%H:%M:%S",x:"locale",X:"locale"};Date.ext.aggregates.z=Date.ext.formats.z(new Date());Date.ext.aggregates.Z=Date.ext.formats.Z(new Date());Date.ext.unsupported={};Date.prototype.strftime=function(a){if(!(this.locale in Date.ext.locales)){if(this.locale.replace(/-[a-zA-Z]+$/,"") in Date.ext.locales){this.locale=this.locale.replace(/-[a-zA-Z]+$/,"")}else{this.locale="en-GB"}}var c=this;while(a.match(/%[cDhnrRtTxXzZ]/)){a=a.replace(/%([cDhnrRtTxXzZ])/g,function(e,d){var g=Date.ext.aggregates[d];return(g=="locale"?Date.ext.locales[c.locale][d]:g)})}var b=a.replace(/%([aAbBCdegGHIjmMpPSuUVwWyY%])/g,function(e,d){var g=Date.ext.formats[d];if(typeof(g)=="string"){return c[g]()}else{if(typeof(g)=="function"){return g.call(c,c)}else{if(typeof(g)=="object"&&typeof(g[0])=="string"){return Date.ext.util.xPad(c[g[0]](),g[1])}else{return d}}}});c=null;return b};"use strict";function RGBColorParser(f){this.ok=false;if(f.charAt(0)=="#"){f=f.substr(1,6)}f=f.replace(/ /g,"");f=f.toLowerCase();var b={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"00ffff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000000",blanchedalmond:"ffebcd",blue:"0000ff",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",cyan:"00ffff",darkblue:"00008b",darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgreen:"006400",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dodgerblue:"1e90ff",feldspar:"d19275",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"ff00ff",gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",green:"008000",greenyellow:"adff2f",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgrey:"d3d3d3",lightgreen:"90ee90",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa",lightslateblue:"8470ff",lightslategray:"778899",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"00ff00",limegreen:"32cd32",linen:"faf0e6",magenta:"ff00ff",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370d8",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080",oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"d87093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",red:"ff0000",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd",slategray:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",violetred:"d02090",wheat:"f5deb3",white:"ffffff",whitesmoke:"f5f5f5",yellow:"ffff00",yellowgreen:"9acd32"};for(var g in b){if(f==g){f=b[g]}}var e=[{re:/^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,example:["rgb(123, 234, 45)","rgb(255,234,245)"],process:function(i){return[parseInt(i[1]),parseInt(i[2]),parseInt(i[3])]}},{re:/^(\w{2})(\w{2})(\w{2})$/,example:["#00ff00","336699"],process:function(i){return[parseInt(i[1],16),parseInt(i[2],16),parseInt(i[3],16)]}},{re:/^(\w{1})(\w{1})(\w{1})$/,example:["#fb0","f0f"],process:function(i){return[parseInt(i[1]+i[1],16),parseInt(i[2]+i[2],16),parseInt(i[3]+i[3],16)]}}];for(var c=0;c255)?255:this.r);this.g=(this.g<0||isNaN(this.g))?0:((this.g>255)?255:this.g);this.b=(this.b<0||isNaN(this.b))?0:((this.b>255)?255:this.b);this.toRGB=function(){return"rgb("+this.r+", "+this.g+", "+this.b+")"};this.toHex=function(){var l=this.r.toString(16);var k=this.g.toString(16);var i=this.b.toString(16);if(l.length==1){l="0"+l}if(k.length==1){k="0"+k}if(i.length==1){i="0"+i}return"#"+l+k+i}}function printStackTrace(b){b=b||{guess:true};var c=b.e||null,e=!!b.guess;var d=new printStackTrace.implementation(),a=d.run(c);return(e)?d.guessAnonymousFunctions(a):a}printStackTrace.implementation=function(){};printStackTrace.implementation.prototype={run:function(a,b){a=a||this.createException();b=b||this.mode(a);if(b==="other"){return this.other(arguments.callee)}else{return this[b](a)}},createException:function(){try{this.undef()}catch(a){return a}},mode:function(a){if(a["arguments"]&&a.stack){return"chrome"}else{if(typeof a.message==="string"&&typeof window!=="undefined"&&window.opera){if(!a.stacktrace){return"opera9"}if(a.message.indexOf("\n")>-1&&a.message.split("\n").length>a.stacktrace.split("\n").length){return"opera9"}if(!a.stack){return"opera10a"}if(a.stacktrace.indexOf("called from line")<0){return"opera10b"}return"opera11"}else{if(a.stack){return"firefox"}}}return"other"},instrumentFunction:function(b,d,e){b=b||window;var a=b[d];b[d]=function c(){e.call(this,printStackTrace().slice(4));return b[d]._instrumented.apply(this,arguments)};b[d]._instrumented=a},deinstrumentFunction:function(a,b){if(a[b].constructor===Function&&a[b]._instrumented&&a[b]._instrumented.constructor===Function){a[b]=a[b]._instrumented}},chrome:function(b){var a=(b.stack+"\n").replace(/^\S[^\(]+?[\n$]/gm,"").replace(/^\s+at\s+/gm,"").replace(/^([^\(]+?)([\n$])/gm,"{anonymous}()@$1$2").replace(/^Object.\s*\(([^\)]+)\)/gm,"{anonymous}()@$1").split("\n");a.pop();return a},firefox:function(a){return a.stack.replace(/(?:\n@:0)?\s+$/m,"").replace(/^\(/gm,"{anonymous}(").split("\n")},opera11:function(g){var a="{anonymous}",h=/^.*line (\d+), column (\d+)(?: in (.+))? in (\S+):$/;var k=g.stacktrace.split("\n"),l=[];for(var c=0,f=k.length;c/,"$1").replace(//,a);l.push(b+"@"+j+" -- "+k[c+1].replace(/^\s+/,""))}}return l},opera10b:function(g){var a="{anonymous}",h=/^(.*)@(.+):(\d+)$/;var j=g.stacktrace.split("\n"),k=[];for(var c=0,f=j.length;c=0){l=l.substr(0,c)}if(l){b=l+b;d=k.exec(b);if(d&&d[1]){return d[1]}d=g.exec(b);if(d&&d[1]){return d[1]}d=h.exec(b);if(d&&d[1]){return d[1]}}}return"(?)"}};CanvasRenderingContext2D.prototype.installPattern=function(e){if(typeof(this.isPatternInstalled)!=="undefined"){throw"Must un-install old line pattern before installing a new one."}this.isPatternInstalled=true;var g=[0,0];var b=[];var f=this.beginPath;var d=this.lineTo;var c=this.moveTo;var a=this.stroke;this.uninstallPattern=function(){this.beginPath=f;this.lineTo=d;this.moveTo=c;this.stroke=a;this.uninstallPattern=undefined;this.isPatternInstalled=undefined};this.beginPath=function(){b=[];f.call(this)};this.moveTo=function(h,i){b.push([[h,i]]);c.call(this,h,i)};this.lineTo=function(h,j){var i=b[b.length-1];i.push([h,j])};this.stroke=function(){if(b.length===0){a.call(this);return}for(var p=0;pt){var q=e[m];if(g[1]){t+=g[1]}else{t+=q}if(t>r){g=[m,t-r];t=r}else{g=[(m+1)%e.length,0]}if(m%2===0){d.call(this,t,0)}else{c.call(this,t,0)}m=(m+1)%e.length}this.restore();l=h,u=s}}a.call(this);b=[]}};CanvasRenderingContext2D.prototype.uninstallPattern=function(){throw"Must install a line pattern before uninstalling it."};var DygraphOptions=(function(){var a=function(b){this.dygraph_=b;this.yAxes_=[];this.xAxis_={};this.series_={};this.global_=this.dygraph_.attrs_;this.user_=this.dygraph_.user_attrs_||{};this.labels_=[];this.highlightSeries_=this.get("highlightSeriesOpts")||{};this.reparseSeries()};a.AXIS_STRING_MAPPINGS_={y:0,Y:0,y1:0,Y1:0,y2:1,Y2:1};a.axisToIndex_=function(b){if(typeof(b)=="string"){if(a.AXIS_STRING_MAPPINGS_.hasOwnProperty(b)){return a.AXIS_STRING_MAPPINGS_[b]}throw"Unknown axis : "+b}if(typeof(b)=="number"){if(b===0||b===1){return b}throw"Dygraphs only supports two y-axes, indexed from 0-1."}if(b){throw"Unknown axis : "+b}return 0};a.prototype.reparseSeries=function(){var g=this.get("labels");if(!g){return}this.labels_=g.slice(1);this.yAxes_=[{series:[],options:{}}];this.xAxis_={options:{}};this.series_={};var h=!this.user_.series;if(h){var c=0;for(var j=0;j1){Dygraph.update(this.yAxes_[1].options,f.y2||{})}Dygraph.update(this.xAxis_.options,f.x||{})};a.prototype.get=function(c){var b=this.getGlobalUser_(c);if(b!==null){return b}return this.getGlobalDefault_(c)};a.prototype.getGlobalUser_=function(b){if(this.user_.hasOwnProperty(b)){return this.user_[b]}return null};a.prototype.getGlobalDefault_=function(b){if(this.global_.hasOwnProperty(b)){return this.global_[b]}if(Dygraph.DEFAULT_ATTRS.hasOwnProperty(b)){return Dygraph.DEFAULT_ATTRS[b]}return null};a.prototype.getForAxis=function(d,e){var f;var i;if(typeof(e)=="number"){f=e;i=f===0?"y":"y2"}else{if(e=="y1"){e="y"}if(e=="y"){f=0}else{if(e=="y2"){f=1}else{if(e=="x"){f=-1}else{throw"Unknown axis "+e}}}i=e}var g=(f==-1)?this.xAxis_:this.yAxes_[f];if(g){var h=g.options;if(h.hasOwnProperty(d)){return h[d]}}var c=this.getGlobalUser_(d);if(c!==null){return c}var b=Dygraph.DEFAULT_ATTRS.axes[i];if(b.hasOwnProperty(d)){return b[d]}return this.getGlobalDefault_(d)};a.prototype.getForSeries=function(c,e){if(e===this.dygraph_.getHighlightSeries()){if(this.highlightSeries_.hasOwnProperty(c)){return this.highlightSeries_[c]}}if(!this.series_.hasOwnProperty(e)){throw"Unknown series: "+e}var d=this.series_[e];var b=d.options;if(b.hasOwnProperty(c)){return b[c]}return this.getForAxis(c,d.yAxis)};a.prototype.numAxes=function(){return this.yAxes_.length};a.prototype.axisForSeries=function(b){return this.series_[b].yAxis};a.prototype.axisOptions=function(b){return this.yAxes_[b].options};a.prototype.seriesForAxis=function(b){return this.yAxes_[b].series};a.prototype.seriesNames=function(){return this.labels_};return a})();"use strict";var DygraphLayout=function(a){this.dygraph_=a;this.points=[];this.setNames=[];this.annotations=[];this.yAxes_=null;this.xTicks_=null;this.yTicks_=null};DygraphLayout.prototype.attr_=function(a){return this.dygraph_.attr_(a)};DygraphLayout.prototype.addDataset=function(a,b){this.points.push(b);this.setNames.push(a)};DygraphLayout.prototype.getPlotArea=function(){return this.area_};DygraphLayout.prototype.computePlotArea=function(){var a={x:0,y:0};a.w=this.dygraph_.width_-a.x-this.attr_("rightGap");a.h=this.dygraph_.height_;var b={chart_div:this.dygraph_.graphDiv,reserveSpaceLeft:function(c){var d={x:a.x,y:a.y,w:c,h:a.h};a.x+=c;a.w-=c;return d},reserveSpaceRight:function(c){var d={x:a.x+a.w-c,y:a.y,w:c,h:a.h};a.w-=c;return d},reserveSpaceTop:function(c){var d={x:a.x,y:a.y,w:a.w,h:c};a.y+=c;a.h-=c;return d},reserveSpaceBottom:function(c){var d={x:a.x,y:a.y+a.h-c,w:a.w,h:c};a.h-=c;return d},chartRect:function(){return{x:a.x,y:a.y,w:a.w,h:a.h}}};this.dygraph_.cascadeEvents_("layout",b);this.area_=a};DygraphLayout.prototype.setAnnotations=function(d){this.annotations=[];var e=this.attr_("xValueParser")||function(a){return a};for(var c=0;c=0)&&(f<=1)){this.xticks.push([f,b])}}this.yticks=[];for(d=0;d=0)&&(f<=1)){this.yticks.push([d,f,b])}}}};DygraphLayout.prototype._evaluateAnnotations=function(){var d;var g={};for(d=0;d=0;e--){if(f.childNodes[e].className==g){f.removeChild(f.childNodes[e])}}var c=document.bgColor;var d=this.dygraph_.graphDiv;while(d!=document){var a=d.currentStyle.backgroundColor;if(a&&a!="transparent"){c=a;break}d=d.parentNode}function b(j){if(j.w===0||j.h===0){return}var i=document.createElement("div");i.className=g;i.style.backgroundColor=c;i.style.position="absolute";i.style.left=j.x+"px";i.style.top=j.y+"px";i.style.width=j.w+"px";i.style.height=j.h+"px";f.appendChild(i)}var h=this.area;b({x:0,y:0,w:h.x,h:this.height});b({x:h.x,y:0,w:this.width-h.x,h:h.y});b({x:h.x+h.w,y:0,w:this.width-h.x-h.w,h:this.height});b({x:h.x,y:h.y+h.h,w:this.width-h.x,h:this.height-h.h-h.y})};DygraphCanvasRenderer._getIteratorPredicate=function(a){return a?DygraphCanvasRenderer._predicateThatSkipsEmptyPoints:null};DygraphCanvasRenderer._predicateThatSkipsEmptyPoints=function(b,a){return b[a].yval!==null};DygraphCanvasRenderer._drawStyledLine=function(i,a,m,q,f,n,d){var h=i.dygraph;var c=h.getOption("stepPlot",i.setName);if(!Dygraph.isArrayLike(q)){q=null}var l=h.getOption("drawGapEdgePoints",i.setName);var o=i.points;var k=Dygraph.createIterator(o,0,o.length,DygraphCanvasRenderer._getIteratorPredicate(h.getOption("connectSeparatedPoints")));var j=q&&(q.length>=2);var p=i.drawingContext;p.save();if(j){p.installPattern(q)}var b=DygraphCanvasRenderer._drawSeries(i,k,m,d,f,l,c,a);DygraphCanvasRenderer._drawPointsOnLine(i,b,n,a,d);if(j){p.uninstallPattern()}p.restore()};DygraphCanvasRenderer._drawSeries=function(v,t,m,h,p,s,g,q){var b=null;var w=null;var k=null;var j;var o;var l=[];var f=true;var n=v.drawingContext;n.beginPath();n.strokeStyle=q;n.lineWidth=m;var c=t.array_;var u=t.end_;var a=t.predicate_;for(var r=t.start_;r=0;C--){if(!D.visibility()[C]){I.splice(C,1)}}var h=(function(){for(var e=0;e=0;u--){var n=I[u];if(!D.getOption("fillGraph",n)){continue}var p=D.getOption("stepPlot",n);var x=q[u];var f=D.axisPropertiesForSeries(n);var d=1+f.minyval*f.yscale;if(d<0){d=0}else{if(d>1){d=1}}d=E.h*d+E.y;var B=c[u];var A=Dygraph.createIterator(B,0,B.length,DygraphCanvasRenderer._getIteratorPredicate(D.getOption("connectSeparatedPoints")));var m=NaN;var l=[-1,-1];var t;var b=new RGBColorParser(x);var H="rgba("+b.r+","+b.g+","+b.b+","+y+")";w.fillStyle=H;w.beginPath();var j,G=true;while(A.hasNext){var v=A.next();if(!Dygraph.isOK(v.y)){m=NaN;if(v.y_stacked!==null&&!isNaN(v.y_stacked)){s[v.canvasx]=E.h*v.y_stacked+E.y}continue}if(k){if(!G&&j==v.xval){continue}else{G=false;j=v.xval}a=s[v.canvasx];var o;if(a===undefined){o=d}else{if(r){o=a[0]}else{o=a}}t=[v.canvasy,o];if(p){if(l[0]===-1){s[v.canvasx]=[v.canvasy,d]}else{s[v.canvasx]=[v.canvasy,l[0]]}}else{s[v.canvasx]=v.canvasy}}else{t=[v.canvasy,d]}if(!isNaN(m)){w.moveTo(m,l[0]);if(p){w.lineTo(v.canvasx,l[0])}else{w.lineTo(v.canvasx,t[0])}if(r&&a){w.lineTo(v.canvasx,a[1])}else{w.lineTo(v.canvasx,t[1])}w.lineTo(m,l[1]);w.closePath()}l=t;m=v.canvasx}r=p;w.fill()}};"use strict";var Dygraph=function(d,c,b,a){this.is_initial_draw_=true;this.readyFns_=[];if(a!==undefined){this.warn("Using deprecated four-argument dygraph constructor");this.__old_init__(d,c,b,a)}else{this.__init__(d,c,b)}};Dygraph.NAME="Dygraph";Dygraph.VERSION="1.0.1";Dygraph.__repr__=function(){return"["+this.NAME+" "+this.VERSION+"]"};Dygraph.toString=function(){return this.__repr__()};Dygraph.DEFAULT_ROLL_PERIOD=1;Dygraph.DEFAULT_WIDTH=480;Dygraph.DEFAULT_HEIGHT=320;Dygraph.ANIMATION_STEPS=12;Dygraph.ANIMATION_DURATION=200;Dygraph.KMB_LABELS=["K","M","B","T","Q"];Dygraph.KMG2_BIG_LABELS=["k","M","G","T","P","E","Z","Y"];Dygraph.KMG2_SMALL_LABELS=["m","u","n","p","f","a","z","y"];Dygraph.numberValueFormatter=function(s,a,u,l){var e=a("sigFigs");if(e!==null){return Dygraph.floatFormat(s,e)}var c=a("digitsAfterDecimal");var o=a("maxNumberWidth");var b=a("labelsKMB");var r=a("labelsKMG2");var q;if(s!==0&&(Math.abs(s)>=Math.pow(10,o)||Math.abs(s)=0;h--,d/=f){if(p>=d){q=Dygraph.round_(s/d,c)+t[h];break}}if(r){var i=String(s.toExponential()).split("e-");if(i.length===2&&i[1]>=3&&i[1]<=24){if(i[1]%3>0){q=Dygraph.round_(i[0]/Dygraph.pow(10,(i[1]%3)),c)}else{q=Number(i[0]).toFixed(2)}q+=m[Math.floor(i[1]/3)-1]}}}return q};Dygraph.numberAxisLabelFormatter=function(a,d,c,b){return Dygraph.numberValueFormatter(a,c,b)};Dygraph.dateString_=function(e){var i=Dygraph.zeropad;var h=new Date(e);var f=""+h.getFullYear();var g=i(h.getMonth()+1);var a=i(h.getDate());var c="";var b=h.getHours()*3600+h.getMinutes()*60+h.getSeconds();if(b){c=" "+Dygraph.hmsString_(e)}return f+"/"+g+"/"+a+c};Dygraph.dateAxisFormatter=function(b,c){if(c>=Dygraph.DECADAL){return b.strftime("%Y")}else{if(c>=Dygraph.MONTHLY){return b.strftime("%b %y")}else{var a=b.getHours()*3600+b.getMinutes()*60+b.getSeconds()+b.getMilliseconds();if(a===0||c>=Dygraph.DAILY){return new Date(b.getTime()+3600*1000).strftime("%d%b")}else{return Dygraph.hmsString_(b.getTime())}}}};Dygraph.Plotters=DygraphCanvasRenderer._Plotters;Dygraph.DEFAULT_ATTRS={highlightCircleSize:3,highlightSeriesOpts:null,highlightSeriesBackgroundAlpha:0.5,labelsDivWidth:250,labelsDivStyles:{},labelsSeparateLines:false,labelsShowZeroValues:true,labelsKMB:false,labelsKMG2:false,showLabelsOnHighlight:true,digitsAfterDecimal:2,maxNumberWidth:6,sigFigs:null,strokeWidth:1,strokeBorderWidth:0,strokeBorderColor:"white",axisTickSize:3,axisLabelFontSize:14,xAxisLabelWidth:50,yAxisLabelWidth:50,rightGap:5,showRoller:false,xValueParser:Dygraph.dateParser,delimiter:",",sigma:2,errorBars:false,fractions:false,wilsonInterval:true,customBars:false,fillGraph:false,fillAlpha:0.15,connectSeparatedPoints:false,stackedGraph:false,stackedGraphNaNFill:"all",hideOverlayOnMouseOut:true,legend:"onmouseover",stepPlot:false,avoidMinZero:false,xRangePad:0,yRangePad:null,drawAxesAtZero:false,titleHeight:28,xLabelHeight:18,yLabelWidth:18,drawXAxis:true,drawYAxis:true,axisLineColor:"black",axisLineWidth:0.3,gridLineWidth:0.3,axisLabelColor:"black",axisLabelFont:"Arial",axisLabelWidth:50,drawYGrid:true,drawXGrid:true,gridLineColor:"rgb(128,128,128)",interactionModel:null,animatedZooms:false,showRangeSelector:false,rangeSelectorHeight:40,rangeSelectorPlotStrokeColor:"#808FAB",rangeSelectorPlotFillColor:"#A7B1C4",plotter:[Dygraph.Plotters.fillPlotter,Dygraph.Plotters.errorPlotter,Dygraph.Plotters.linePlotter],plugins:[],axes:{x:{pixelsPerLabel:60,axisLabelFormatter:Dygraph.dateAxisFormatter,valueFormatter:Dygraph.dateString_,drawGrid:true,independentTicks:true,ticker:null},y:{pixelsPerLabel:30,valueFormatter:Dygraph.numberValueFormatter,axisLabelFormatter:Dygraph.numberAxisLabelFormatter,drawGrid:true,independentTicks:true,ticker:null},y2:{pixelsPerLabel:30,valueFormatter:Dygraph.numberValueFormatter,axisLabelFormatter:Dygraph.numberAxisLabelFormatter,drawGrid:false,independentTicks:false,ticker:null}}};Dygraph.HORIZONTAL=1;Dygraph.VERTICAL=2;Dygraph.PLUGINS=[];Dygraph.addedAnnotationCSS=false;Dygraph.prototype.__old_init__=function(f,d,e,b){if(e!==null){var a=["Date"];for(var c=0;c=0;d--){var f=a[d][0];var h=a[d][1];h.call(f,g);if(g.propagationStopped){break}}}return g.defaultPrevented};Dygraph.prototype.isZoomed=function(a){if(a===null||a===undefined){return this.zoomed_x_||this.zoomed_y_}if(a==="x"){return this.zoomed_x_}if(a==="y"){return this.zoomed_y_}throw"axis parameter is ["+a+"] must be null, 'x' or 'y'."};Dygraph.prototype.toString=function(){var a=this.maindiv_;var b=(a&&a.id)?a.id:a;return"[Dygraph "+b+"]"};Dygraph.prototype.attr_=function(b,a){return a?this.attributes_.getForSeries(b,a):this.attributes_.get(b)};Dygraph.prototype.getOption=function(a,b){return this.attr_(a,b)};Dygraph.prototype.getOptionForAxis=function(a,b){return this.attributes_.getForAxis(a,b)};Dygraph.prototype.optionsViewForAxis_=function(b){var a=this;return function(c){var d=a.user_attrs_.axes;if(d&&d[b]&&d[b].hasOwnProperty(c)){return d[b][c]}if(typeof(a.user_attrs_[c])!="undefined"){return a.user_attrs_[c]}d=a.attrs_.axes;if(d&&d[b]&&d[b].hasOwnProperty(c)){return d[b][c]}if(b=="y"&&a.axes_[0].hasOwnProperty(c)){return a.axes_[0][c]}else{if(b=="y2"&&a.axes_[1].hasOwnProperty(c)){return a.axes_[1][c]}}return a.attr_(c)}};Dygraph.prototype.rollPeriod=function(){return this.rollPeriod_};Dygraph.prototype.xAxisRange=function(){return this.dateWindow_?this.dateWindow_:this.xAxisExtremes()};Dygraph.prototype.xAxisExtremes=function(){var d=this.attr_("xRangePad")/this.plotter_.area.w;if(this.numRows()===0){return[0-d,1+d]}var c=this.rawData_[0][0];var b=this.rawData_[this.rawData_.length-1][0];if(d){var a=b-c;c-=a*d;b+=a*d}return[c,b]};Dygraph.prototype.yAxisRange=function(a){if(typeof(a)=="undefined"){a=0}if(a<0||a>=this.axes_.length){return null}var b=this.axes_[a];return[b.computedValueRange[0],b.computedValueRange[1]]};Dygraph.prototype.yAxisRanges=function(){var a=[];for(var b=0;bthis.rawData_.length){return null}if(a<0||a>this.rawData_[b].length){return null}return this.rawData_[b][a]};Dygraph.prototype.createInterface_=function(){var a=this.maindiv_;this.graphDiv=document.createElement("div");this.graphDiv.style.textAlign="left";a.appendChild(this.graphDiv);this.canvas_=Dygraph.createCanvas();this.canvas_.style.position="absolute";this.hidden_=this.createPlotKitCanvas_(this.canvas_);this.resizeElements_();this.canvas_ctx_=Dygraph.getContext(this.canvas_);this.hidden_ctx_=Dygraph.getContext(this.hidden_);this.graphDiv.appendChild(this.hidden_);this.graphDiv.appendChild(this.canvas_);this.mouseEventElement_=this.createMouseEventElement_();this.layout_=new DygraphLayout(this);var b=this;this.mouseMoveHandler_=function(c){b.mouseMove_(c)};this.mouseOutHandler_=function(f){var d=f.target||f.fromElement;var c=f.relatedTarget||f.toElement;if(Dygraph.isNodeContainedBy(d,b.graphDiv)&&!Dygraph.isNodeContainedBy(c,b.graphDiv)){b.mouseOut_(f)}};this.addAndTrackEvent(window,"mouseout",this.mouseOutHandler_);this.addAndTrackEvent(this.mouseEventElement_,"mousemove",this.mouseMoveHandler_);if(!this.resizeHandler_){this.resizeHandler_=function(c){b.resize()};this.addAndTrackEvent(window,"resize",this.resizeHandler_)}};Dygraph.prototype.resizeElements_=function(){this.graphDiv.style.width=this.width_+"px";this.graphDiv.style.height=this.height_+"px";this.canvas_.width=this.width_;this.canvas_.height=this.height_;this.canvas_.style.width=this.width_+"px";this.canvas_.style.height=this.height_+"px";this.hidden_.width=this.width_;this.hidden_.height=this.height_;this.hidden_.style.width=this.width_+"px";this.hidden_.style.height=this.height_+"px"};Dygraph.prototype.destroy=function(){this.canvas_ctx_.restore();this.hidden_ctx_.restore();var a=function(c){while(c.hasChildNodes()){a(c.firstChild);c.removeChild(c.firstChild)}};this.removeTrackedEvents_();Dygraph.removeEvent(window,"mouseout",this.mouseOutHandler_);Dygraph.removeEvent(this.mouseEventElement_,"mousemove",this.mouseMoveHandler_);Dygraph.removeEvent(window,"resize",this.resizeHandler_);this.resizeHandler_=null;a(this.maindiv_);var b=function(c){for(var d in c){if(typeof(c[d])==="object"){c[d]=null}}};b(this.layout_);b(this.plotter_);b(this)};Dygraph.prototype.createPlotKitCanvas_=function(a){var b=Dygraph.createCanvas();b.style.position="absolute";b.style.top=a.style.top;b.style.left=a.style.left;b.width=this.width_;b.height=this.height_;b.style.width=this.width_+"px";b.style.height=this.height_+"px";return b};Dygraph.prototype.createMouseEventElement_=function(){if(this.isUsingExcanvas_){var a=document.createElement("div");a.style.position="absolute";a.style.backgroundColor="white";a.style.filter="alpha(opacity=0)";a.style.width=this.width_+"px";a.style.height=this.height_+"px";this.graphDiv.appendChild(a);return a}else{return this.canvas_}};Dygraph.prototype.setColors_=function(){var g=this.getLabels();var e=g.length-1;this.colors_=[];this.colorsMap_={};var a=this.attr_("colors");var d;if(!a){var c=this.attr_("colorSaturation")||1;var b=this.attr_("colorValue")||0.5;var k=Math.ceil(e/2);for(d=1;d<=e;d++){if(!this.visibility()[d-1]){continue}var h=d%2?Math.ceil(d/2):(k+d/2);var f=(1*h/(1+e));var j=Dygraph.hsvToRGB(f,c,b);this.colors_.push(j);this.colorsMap_[g[d]]=j}}else{for(d=0;d=0;--b){var m=this.layout_.points[b];for(var g=0;g=l.length){continue}var m=l[e];if(!Dygraph.isValidPoint(m)){continue}var j=m.canvasy;if(i>m.canvasx&&e+10){var a=(i-m.canvasx)/o;j+=a*(k.canvasy-m.canvasy)}}}else{if(i0){var n=l[e-1];if(Dygraph.isValidPoint(n)){var o=m.canvasx-n.canvasx;if(o>0){var a=(m.canvasx-i)/o;j+=a*(n.canvasy-m.canvasy)}}}}if(d===0||j=0){var j=0;var k=this.attr_("labels");for(h=1;hj){j=c}}var l=this.previousVerticalX_;n.clearRect(l-j-1,0,2*j+2,this.height_)}}if(this.isUsingExcanvas_&&this.currentZoomRectArgs_){Dygraph.prototype.drawZoomRect_.apply(this,this.currentZoomRectArgs_)}if(this.selPoints_.length>0){var b=this.selPoints_[0].canvasx;n.save();for(h=0;h=0){if(f!=this.lastRow_){e=true}this.lastRow_=f;for(var d=0;d=0){e=true}this.lastRow_=-1}if(this.selPoints_.length){this.lastx_=this.selPoints_[0].xval}else{this.lastx_=-1}if(h!==undefined){if(this.highlightSet_!==h){e=true}this.highlightSet_=h}if(g!==undefined){this.lockedSet_=g}if(e){this.updateSelection_(undefined)}return e};Dygraph.prototype.mouseOut_=function(a){if(this.attr_("unhighlightCallback")){this.attr_("unhighlightCallback")(a)}if(this.attr_("hideOverlayOnMouseOut")&&!this.lockedSet_){this.clearSelection()}};Dygraph.prototype.clearSelection=function(){this.cascadeEvents_("deselect",{});this.lockedSet_=false;if(this.fadeLevel){this.animateSelection_(-1);return}this.canvas_ctx_.clearRect(0,0,this.width_,this.height_);this.fadeLevel=0;this.selPoints_=[];this.lastx_=-1;this.lastRow_=-1;this.highlightSet_=null};Dygraph.prototype.getSelection=function(){if(!this.selPoints_||this.selPoints_.length<1){return -1}for(var c=0;cg){a=g}if(ef){f=e}if(h===null||af){f=g}if(h===null||g=i){return}for(var p=i;po[1]){o[1]=a}if(a=1;u--){if(!this.visibility()[u-1]){continue}if(f){l=w[u];var z=f[0];var j=f[1];var g=null,y=null;for(r=0;r=z&&g===null){g=r}if(l[r][0]<=j){y=r}}if(g===null){g=0}var e=g;var d=true;while(d&&e>0){e--;d=b(l[e])}if(y===null){y=l.length-1}var c=y;d=true;while(d&&c0){this.setIndexByName_[g[0]]=0}var e=0;for(var f=1;f0){var a=this.readyFns_.pop();a(this)}}};Dygraph.prototype.computeYAxes_=function(){var b,d,c,f,a;if(this.axes_!==undefined&&this.user_attrs_.hasOwnProperty("valueRange")===false){b=[];for(c=0;c0){D=0}if(A<0){A=0}}if(D==Infinity){D=0}if(A==-Infinity){A=1}x=A-D;if(x===0){if(A!==0){x=Math.abs(A)}else{A=1;x=1}}var h,H;if(C){if(b){h=A+B*x;H=D}else{var E=Math.exp(Math.log(x)*B);h=A*E;H=D/E}}else{h=A+B*x;H=D-B*x;if(b&&!this.attr_("avoidMinZero")){if(H<0&&D>=0){H=0}if(h>0&&A<=0){h=0}}}c.extremeRange=[H,h]}if(c.valueWindow){c.computedValueRange=[c.valueWindow[0],c.valueWindow[1]]}else{if(c.valueRange){var e=g(c.valueRange[0])?c.extremeRange[0]:c.valueRange[0];var d=g(c.valueRange[1])?c.extremeRange[1]:c.valueRange[1];if(!b){if(c.logscale){var E=Math.exp(Math.log(x)*B);e*=E;d/=E}else{x=d-e;e-=x*B;d+=x*B}}c.computedValueRange=[e,d]}else{c.computedValueRange=c.extremeRange}}if(l){c.independentTicks=l;var r=this.optionsViewForAxis_("y"+(y?"2":""));var F=r("ticker");c.ticks=F(c.computedValueRange[0],c.computedValueRange[1],this.height_,r,this);if(!p){p=c}}}if(p===undefined){throw ('Configuration Error: At least one axis has to have the "independentTicks" option activated.')}for(var y=0;y=0){k-=l[z-d][1][0];h-=l[z-d][1][1]}var C=l[z][0];var w=h?k/h:0;if(this.attr_("errorBars")){if(this.attr_("wilsonInterval")){if(h){var s=w<0?0:w,u=h;var B=t*Math.sqrt(s*(1-s)/u+t*t/(4*u*u));var a=1+t*t/h;G=(s+t*t/(2*h)-B)/a;o=(s+t*t/(2*h)+B)/a;b[z]=[C,[s*e,(s-G)*e,(o-s)*e]]}else{b[z]=[C,[0,0,0]]}}else{A=h?t*Math.sqrt(w*(1-w)/h):1;b[z]=[C,[e*w,e*A,e*A]]}}else{b[z]=[C,e*w]}}}else{if(this.attr_("customBars")){G=0;var D=0;o=0;var g=0;for(z=0;z=0){var r=l[z-d];if(r[1][1]!==null&&!isNaN(r[1][1])){G-=r[1][0];D-=r[1][1];o-=r[1][2];g-=1}}if(g){b[z]=[l[z][0],[1*D/g,1*(D-G)/g,1*(o-D)/g]]}else{b[z]=[l[z][0],[null,null,null]]}}}else{if(!this.attr_("errorBars")){if(d==1){return l}for(z=0;z0&&(b[c-1]!="e"&&b[c-1]!="E"))||b.indexOf("/")>=0||isNaN(parseFloat(b))){a=true}else{if(b.length==8&&b>"19700101"&&b<"20371231"){a=true}}this.setXAxisOptions_(a)};Dygraph.prototype.setXAxisOptions_=function(a){if(a){this.attrs_.xValueParser=Dygraph.dateParser;this.attrs_.axes.x.valueFormatter=Dygraph.dateString_;this.attrs_.axes.x.ticker=Dygraph.dateTicker;this.attrs_.axes.x.axisLabelFormatter=Dygraph.dateAxisFormatter}else{this.attrs_.xValueParser=function(b){return parseFloat(b)};this.attrs_.axes.x.valueFormatter=function(b){return b};this.attrs_.axes.x.ticker=Dygraph.numericLinearTicks;this.attrs_.axes.x.axisLabelFormatter=this.attrs_.axes.x.valueFormatter}};Dygraph.prototype.parseFloat_=function(a,c,b){var e=parseFloat(a);if(!isNaN(e)){return e}if(/^ *$/.test(a)){return null}if(/^ *nan *$/i.test(a)){return NaN}var d="Unable to parse '"+a+"' as a number";if(b!==null&&c!==null){d+=" on line "+(1+c)+" ('"+b+"') of CSV."}this.error(d);return null};Dygraph.prototype.parseCSV_=function(t){var r=[];var s=Dygraph.detectLineDelimiter(t);var a=t.split(s||"\n");var g,k;var p=this.attr_("delimiter");if(a[0].indexOf(p)==-1&&a[0].indexOf("\t")>=0){p="\t"}var b=0;if(!("labels" in this.user_attrs_)){b=1;this.attrs_.labels=a[0].split(p);this.attributes_.reparseSeries()}var o=0;var m;var q=false;var c=this.attr_("labels").length;var f=false;for(var l=b;l0&&h[0]0){j=String.fromCharCode(65+(i-1)%26)+j.toLowerCase();i=Math.floor((i-1)/26)}return j};var h=w.getNumberOfColumns();var g=w.getNumberOfRows();var f=w.getColumnType(0);if(f=="date"||f=="datetime"){this.attrs_.xValueParser=Dygraph.dateParser;this.attrs_.axes.x.valueFormatter=Dygraph.dateString_;this.attrs_.axes.x.ticker=Dygraph.dateTicker;this.attrs_.axes.x.axisLabelFormatter=Dygraph.dateAxisFormatter}else{if(f=="number"){this.attrs_.xValueParser=function(i){return parseFloat(i)};this.attrs_.axes.x.valueFormatter=function(i){return i};this.attrs_.axes.x.ticker=Dygraph.numericLinearTicks;this.attrs_.axes.x.axisLabelFormatter=this.attrs_.axes.x.valueFormatter}else{this.error("only 'date', 'datetime' and 'number' types are supported for column 1 of DataTable input (Got '"+f+"')");return null}}var m=[];var t={};var s=false;var q,o;for(q=1;q0&&e[0]0){this.setAnnotations(a,true)}this.attributes_.reparseSeries()};Dygraph.prototype.start_=function(){var d=this.file_;if(typeof d=="function"){d=d()}if(Dygraph.isArrayLike(d)){this.rawData_=this.parseArray_(d);this.predraw_()}else{if(typeof d=="object"&&typeof d.getColumnRange=="function"){this.parseDataTable_(d);this.predraw_()}else{if(typeof d=="string"){var c=Dygraph.detectLineDelimiter(d);if(c){this.loadedEvent_(d)}else{var b;if(window.XMLHttpRequest){b=new XMLHttpRequest()}else{b=new ActiveXObject("Microsoft.XMLHTTP")}var a=this;b.onreadystatechange=function(){if(b.readyState==4){if(b.status===200||b.status===0){a.loadedEvent_(b.responseText)}}};b.open("GET",d,true);b.send(null)}}else{this.error("Unknown data format: "+(typeof d))}}}};Dygraph.prototype.updateOptions=function(e,b){if(typeof(b)=="undefined"){b=false}var d=e.file;var c=Dygraph.mapLegacyOptions_(e);if("rollPeriod" in c){this.rollPeriod_=c.rollPeriod}if("dateWindow" in c){this.dateWindow_=c.dateWindow;if(!("isZoomedIgnoreProgrammaticZoom" in c)){this.zoomed_x_=(c.dateWindow!==null)}}if("valueRange" in c&&!("isZoomedIgnoreProgrammaticZoom" in c)){this.zoomed_y_=(c.valueRange!==null)}var a=Dygraph.isPixelChangingOptionList(this.attr_("labels"),c);Dygraph.updateDeep(this.user_attrs_,c);this.attributes_.reparseSeries();if(d){this.file_=d;if(!b){this.start_()}}else{if(!b){if(a){this.predraw_()}else{this.renderGraph_(false)}}}};Dygraph.mapLegacyOptions_=function(c){var a={};for(var b in c){if(b=="file"){continue}if(c.hasOwnProperty(b)){a[b]=c[b]}}var e=function(g,f,h){if(!a.axes){a.axes={}}if(!a.axes[g]){a.axes[g]={}}a.axes[g][f]=h};var d=function(f,g,h){if(typeof(c[f])!="undefined"){Dygraph.warn("Option "+f+" is deprecated. Use the "+h+" option for the "+g+" axis instead. (e.g. { axes : { "+g+" : { "+h+" : ... } } } (see http://dygraphs.com/per-axis.html for more information.");e(g,h,c[f]);delete a[f]}};d("xValueFormatter","x","valueFormatter");d("pixelsPerXLabel","x","pixelsPerLabel");d("xAxisLabelFormatter","x","axisLabelFormatter");d("xTicker","x","ticker");d("yValueFormatter","y","valueFormatter");d("pixelsPerYLabel","y","pixelsPerLabel");d("yAxisLabelFormatter","y","axisLabelFormatter");d("yTicker","y","ticker");return a};Dygraph.prototype.resize=function(d,b){if(this.resize_lock){return}this.resize_lock=true;if((d===null)!=(b===null)){this.warn("Dygraph.resize() should be called with zero parameters or two non-NULL parameters. Pretending it was zero.");d=b=null}var a=this.width_;var c=this.height_;if(d){this.maindiv_.style.width=d+"px";this.maindiv_.style.height=b+"px";this.width_=d;this.height_=b}else{this.width_=this.maindiv_.clientWidth;this.height_=this.maindiv_.clientHeight}if(a!=this.width_||c!=this.height_){this.resizeElements_();this.predraw_()}this.resize_lock=false};Dygraph.prototype.adjustRoll=function(a){this.rollPeriod_=a;this.predraw_()};Dygraph.prototype.visibility=function(){if(!this.attr_("visibility")){this.attrs_.visibility=[]}while(this.attr_("visibility").length=a.length){this.warn("invalid series number in setVisibility: "+b)}else{a[b]=c;this.predraw_()}};Dygraph.prototype.size=function(){return{width:this.width_,height:this.height_}};Dygraph.prototype.setAnnotations=function(b,a){Dygraph.addAnnotationRule();this.annotations_=b;if(!this.layout_){this.warn("Tried to setAnnotations before dygraph was ready. Try setting them in a ready() block. See dygraphs.com/tests/annotation.html");return}this.layout_.setAnnotations(this.annotations_);if(!a){this.predraw_()}};Dygraph.prototype.annotations=function(){return this.annotations_};Dygraph.prototype.getLabels=function(){var a=this.attr_("labels");return a?a.slice():null};Dygraph.prototype.indexFromSetName=function(a){return this.setIndexByName_[a]};Dygraph.prototype.ready=function(a){if(this.is_initial_draw_){this.readyFns_.push(a)}else{a(this)}};Dygraph.addAnnotationRule=function(){if(Dygraph.addedAnnotationCSS){return}var f="border: 1px solid black; background-color: white; text-align: center;";var e=document.createElement("style");e.type="text/css";document.getElementsByTagName("head")[0].appendChild(e);for(var b=0;bb){return -1}if(i===null||i===undefined){i=0}var h=function(j){return j>=0&&ja){if(i>0){f=g-1;if(h(f)&&d[f]a){return g}}return Dygraph.binarySearch(a,d,i,g+1,b)}}}return -1};Dygraph.dateParser=function(a){var b;var c;if(a.search("-")==-1||a.search("T")!=-1||a.search("Z")!=-1){c=Dygraph.dateStrToMillis(a);if(c&&!isNaN(c)){return c}}if(a.search("-")!=-1){b=a.replace("-","/","g");while(b.search("-")!=-1){b=b.replace("-","/")}c=Dygraph.dateStrToMillis(b)}else{if(a.length==8){b=a.substr(0,4)+"/"+a.substr(4,2)+"/"+a.substr(6,2);c=Dygraph.dateStrToMillis(b)}else{c=Dygraph.dateStrToMillis(a)}}if(!c||isNaN(c)){Dygraph.error("Couldn't parse "+a+" as a date")}return c};Dygraph.dateStrToMillis=function(a){return new Date(a).getTime()};Dygraph.update=function(b,c){if(typeof(c)!="undefined"&&c!==null){for(var a in c){if(c.hasOwnProperty(a)){b[a]=c[a]}}}return b};Dygraph.updateDeep=function(b,d){function c(e){return(typeof Node==="object"?e instanceof Node:typeof e==="object"&&typeof e.nodeType==="number"&&typeof e.nodeName==="string")}if(typeof(d)!="undefined"&&d!==null){for(var a in d){if(d.hasOwnProperty(a)){if(d[a]===null){b[a]=null}else{if(Dygraph.isArrayLike(d[a])){b[a]=d[a].slice()}else{if(c(d[a])){b[a]=d[a]}else{if(typeof(d[a])=="object"){if(typeof(b[a])!="object"||b[a]===null){b[a]={}}Dygraph.updateDeep(b[a],d[a])}else{b[a]=d[a]}}}}}}}return b};Dygraph.isArrayLike=function(b){var a=typeof(b);if((a!="object"&&!(a=="function"&&typeof(b.item)=="function"))||b===null||typeof(b.length)!="number"||b.nodeType===3){return false}return true};Dygraph.isDateLike=function(a){if(typeof(a)!="object"||a===null||typeof(a.getTime)!="function"){return false}return true};Dygraph.clone=function(c){var b=[];for(var a=0;a=g){return}Dygraph.requestAnimFrame.call(window,function(){var l=new Date().getTime();var j=l-b;d=i;i=Math.floor(j/f);var k=i-d;var m=(i+k)>e;if(m||(i>=e)){h(e);a()}else{if(k!==0){h(i)}c()}})})()};Dygraph.isPixelChangingOptionList=function(h,e){var d={annotationClickHandler:true,annotationDblClickHandler:true,annotationMouseOutHandler:true,annotationMouseOverHandler:true,axisLabelColor:true,axisLineColor:true,axisLineWidth:true,clickCallback:true,digitsAfterDecimal:true,drawCallback:true,drawHighlightPointCallback:true,drawPoints:true,drawPointCallback:true,drawXGrid:true,drawYGrid:true,fillAlpha:true,gridLineColor:true,gridLineWidth:true,hideOverlayOnMouseOut:true,highlightCallback:true,highlightCircleSize:true,interactionModel:true,isZoomedIgnoreProgrammaticZoom:true,labelsDiv:true,labelsDivStyles:true,labelsDivWidth:true,labelsKMB:true,labelsKMG2:true,labelsSeparateLines:true,labelsShowZeroValues:true,legend:true,maxNumberWidth:true,panEdgeFraction:true,pixelsPerYLabel:true,pointClickCallback:true,pointSize:true,rangeSelectorPlotFillColor:true,rangeSelectorPlotStrokeColor:true,showLabelsOnHighlight:true,showRoller:true,sigFigs:true,strokeWidth:true,underlayCallback:true,unhighlightCallback:true,xAxisLabelFormatter:true,xTicker:true,xValueFormatter:true,yAxisLabelFormatter:true,yValueFormatter:true,zoomCallback:true};var a=false;var b={};if(h){for(var f=1;fc.boundedDates[1]){h=h-(a-c.boundedDates[1]);a=h+c.dateRange}}k.dateWindow_=[h,a];if(c.is2DPan){var d=c.dragEndY-c.dragStartY;for(var j=0;j=10&&e.dragDirection==Dygraph.HORIZONTAL){var f=Math.min(e.dragStartX,e.dragEndX),k=Math.max(e.dragStartX,e.dragEndX);f=Math.max(f,b.x);k=Math.min(k,b.x+b.w);if(f=10&&e.dragDirection==Dygraph.VERTICAL){var j=Math.min(e.dragStartY,e.dragEndY),a=Math.max(e.dragStartY,e.dragEndY);j=Math.max(j,b.y);a=Math.min(a,b.y+b.h);if(j1){d.startTimeForDoubleTapMs=null}var h=[];for(var c=0;c=2){d.initialPinchCenter={pageX:0.5*(h[0].pageX+h[1].pageX),pageY:0.5*(h[0].pageY+h[1].pageY),dataX:0.5*(h[0].dataX+h[1].dataX),dataY:0.5*(h[0].dataY+h[1].dataY)};var a=180/Math.PI*Math.atan2(d.initialPinchCenter.pageY-h[0].pageY,h[0].pageX-d.initialPinchCenter.pageX);a=Math.abs(a);if(a>90){a=90-a}d.touchDirections={x:(a<(90-45/2)),y:(a>45/2)}}}d.initialRange={x:e.xAxisRange(),y:e.yAxisRange()}};Dygraph.Interaction.moveTouch=function(n,q,d){d.startTimeForDoubleTapMs=null;var p,l=[];for(p=0;p=2){var e=(a[1].pageX-j.pageX);w=(l[1].pageX-h.pageX)/e;var v=(a[1].pageY-j.pageY);c=(l[1].pageY-h.pageY)/v}}w=Math.min(8,Math.max(0.125,w));c=Math.min(8,Math.max(0.125,c));var u=false;if(d.touchDirections.x){q.dateWindow_=[j.dataX-m.dataX+(d.initialRange.x[0]-j.dataX)/w,j.dataX-m.dataX+(d.initialRange.x[1]-j.dataX)/w];u=true}if(d.touchDirections.y){for(p=0;p<1;p++){var b=q.axes_[p];var s=q.attributes_.getForAxis("logscale",p);if(s){}else{b.valueWindow=[j.dataY-m.dataY+(d.initialRange.y[0]-j.dataY)/c,j.dataY-m.dataY+(d.initialRange.y[1]-j.dataY)/c];u=true}}}q.drawGraph_(false);if(u&&l.length>1&&q.attr_("zoomCallback")){var r=q.xAxisRange();q.attr_("zoomCallback")(r[0],r[1],q.yAxisRanges())}};Dygraph.Interaction.endTouch=function(e,d,c){if(e.touches.length!==0){Dygraph.Interaction.startTouch(e,d,c)}else{if(e.changedTouches.length==1){var a=new Date().getTime();var b=e.changedTouches[0];if(c.startTimeForDoubleTapMs&&a-c.startTimeForDoubleTapMs<500&&c.doubleTapX&&Math.abs(c.doubleTapX-b.screenX)<50&&c.doubleTapY&&Math.abs(c.doubleTapY-b.screenY)<50){d.resetZoom()}else{c.startTimeForDoubleTapMs=a;c.doubleTapX=b.screenX;c.doubleTapY=b.screenY}}}};Dygraph.Interaction.defaultModel={mousedown:function(c,b,a){if(c.button&&c.button==2){return}a.initializeMouseDown(c,b,a);if(c.altKey||c.shiftKey){Dygraph.startPan(c,b,a)}else{Dygraph.startZoom(c,b,a)}},mousemove:function(c,b,a){if(a.isZooming){Dygraph.moveZoom(c,b,a)}else{if(a.isPanning){Dygraph.movePan(c,b,a)}}},mouseup:function(c,b,a){if(a.isZooming){Dygraph.endZoom(c,b,a)}else{if(a.isPanning){Dygraph.endPan(c,b,a)}}},touchstart:function(c,b,a){Dygraph.Interaction.startTouch(c,b,a)},touchmove:function(c,b,a){Dygraph.Interaction.moveTouch(c,b,a)},touchend:function(c,b,a){Dygraph.Interaction.endTouch(c,b,a)},mouseout:function(c,b,a){if(a.isZooming){a.dragEndX=null;a.dragEndY=null;b.clearZoomRect_()}},dblclick:function(c,b,a){if(a.cancelNextDblclick){a.cancelNextDblclick=false;return}if(c.altKey||c.shiftKey){return}b.resetZoom()}};Dygraph.DEFAULT_ATTRS.interactionModel=Dygraph.Interaction.defaultModel;Dygraph.defaultInteractionModel=Dygraph.Interaction.defaultModel;Dygraph.endZoom=Dygraph.Interaction.endZoom;Dygraph.moveZoom=Dygraph.Interaction.moveZoom;Dygraph.startZoom=Dygraph.Interaction.startZoom;Dygraph.endPan=Dygraph.Interaction.endPan;Dygraph.movePan=Dygraph.Interaction.movePan;Dygraph.startPan=Dygraph.Interaction.startPan;Dygraph.Interaction.nonInteractiveModel_={mousedown:function(c,b,a){a.initializeMouseDown(c,b,a)},mouseup:function(c,b,a){a.dragEndX=b.dragGetX_(c,a);a.dragEndY=b.dragGetY_(c,a);var e=Math.abs(a.dragEndX-a.dragStartX);var d=Math.abs(a.dragEndY-a.dragStartY);if(e<2&&d<2&&b.lastx_!==undefined&&b.lastx_!=-1){Dygraph.Interaction.treatMouseOpAsClick(b,c,a)}}};Dygraph.Interaction.dragIsPanInteractionModel={mousedown:function(c,b,a){a.initializeMouseDown(c,b,a);Dygraph.startPan(c,b,a)},mousemove:function(c,b,a){if(a.isPanning){Dygraph.movePan(c,b,a)}},mouseup:function(c,b,a){if(a.isPanning){Dygraph.endPan(c,b,a)}}};"use strict";Dygraph.TickList=undefined;Dygraph.Ticker=undefined;Dygraph.numericLinearTicks=function(d,c,i,g,f,h){var e=function(a){if(a==="logscale"){return false}return g(a)};return Dygraph.numericTicks(d,c,i,e,f,h)};Dygraph.numericTicks=function(F,E,u,p,d,q){var z=(p("pixelsPerLabel"));var G=[];var C,A,t,y;if(q){for(C=0;C=y/4){for(var r=H;r>=l;r--){var m=Dygraph.PREFERRED_LOG_TICK_VALUES[r];var k=Math.log(m/F)/Math.log(E/F)*u;var D={v:m};if(s===null){s={tickValue:m,pixel_coord:k}}else{if(Math.abs(k-s.pixel_coord)>=z){s={tickValue:m,pixel_coord:k}}else{D.label=""}}G.push(D)}G.reverse()}}if(G.length===0){var g=p("labelsKMG2");var n,h;if(g){n=[1,2,4,8,16,32,64,128,256];h=16}else{n=[1,2,5,10,20,50,100];h=10}var w=Math.ceil(u/z);var o=Math.abs(E-F)/w;var v=Math.floor(Math.log(o)/Math.log(h));var f=Math.pow(h,v);var I,x,c,e;for(A=0;Az){break}}if(x>c){I*=-1}for(C=0;C=0){return Dygraph.getDateAxis(e,c,d,g,f)}else{return[]}};Dygraph.SECONDLY=0;Dygraph.TWO_SECONDLY=1;Dygraph.FIVE_SECONDLY=2;Dygraph.TEN_SECONDLY=3;Dygraph.THIRTY_SECONDLY=4;Dygraph.MINUTELY=5;Dygraph.TWO_MINUTELY=6;Dygraph.FIVE_MINUTELY=7;Dygraph.TEN_MINUTELY=8;Dygraph.THIRTY_MINUTELY=9;Dygraph.HOURLY=10;Dygraph.TWO_HOURLY=11;Dygraph.SIX_HOURLY=12;Dygraph.DAILY=13;Dygraph.WEEKLY=14;Dygraph.MONTHLY=15;Dygraph.QUARTERLY=16;Dygraph.BIANNUAL=17;Dygraph.ANNUAL=18;Dygraph.DECADAL=19;Dygraph.CENTENNIAL=20;Dygraph.NUM_GRANULARITIES=21;Dygraph.SHORT_SPACINGS=[];Dygraph.SHORT_SPACINGS[Dygraph.SECONDLY]=1000*1;Dygraph.SHORT_SPACINGS[Dygraph.TWO_SECONDLY]=1000*2;Dygraph.SHORT_SPACINGS[Dygraph.FIVE_SECONDLY]=1000*5;Dygraph.SHORT_SPACINGS[Dygraph.TEN_SECONDLY]=1000*10;Dygraph.SHORT_SPACINGS[Dygraph.THIRTY_SECONDLY]=1000*30;Dygraph.SHORT_SPACINGS[Dygraph.MINUTELY]=1000*60;Dygraph.SHORT_SPACINGS[Dygraph.TWO_MINUTELY]=1000*60*2;Dygraph.SHORT_SPACINGS[Dygraph.FIVE_MINUTELY]=1000*60*5;Dygraph.SHORT_SPACINGS[Dygraph.TEN_MINUTELY]=1000*60*10;Dygraph.SHORT_SPACINGS[Dygraph.THIRTY_MINUTELY]=1000*60*30;Dygraph.SHORT_SPACINGS[Dygraph.HOURLY]=1000*3600;Dygraph.SHORT_SPACINGS[Dygraph.TWO_HOURLY]=1000*3600*2;Dygraph.SHORT_SPACINGS[Dygraph.SIX_HOURLY]=1000*3600*6;Dygraph.SHORT_SPACINGS[Dygraph.DAILY]=1000*86400;Dygraph.SHORT_SPACINGS[Dygraph.WEEKLY]=1000*604800;Dygraph.LONG_TICK_PLACEMENTS=[];Dygraph.LONG_TICK_PLACEMENTS[Dygraph.MONTHLY]={months:[0,1,2,3,4,5,6,7,8,9,10,11],year_mod:1};Dygraph.LONG_TICK_PLACEMENTS[Dygraph.QUARTERLY]={months:[0,3,6,9],year_mod:1};Dygraph.LONG_TICK_PLACEMENTS[Dygraph.BIANNUAL]={months:[0,6],year_mod:1};Dygraph.LONG_TICK_PLACEMENTS[Dygraph.ANNUAL]={months:[0],year_mod:1};Dygraph.LONG_TICK_PLACEMENTS[Dygraph.DECADAL]={months:[0],year_mod:10};Dygraph.LONG_TICK_PLACEMENTS[Dygraph.CENTENNIAL]={months:[0],year_mod:100};Dygraph.PREFERRED_LOG_TICK_VALUES=function(){var c=[];for(var b=-39;b<=39;b++){var a=Math.pow(10,b);for(var d=1;d<=9;d++){var e=a*d;c.push(e)}}return c}();Dygraph.pickDateTickGranularity=function(d,c,j,h){var g=(h("pixelsPerLabel"));for(var f=0;f=g){return f}}return -1};Dygraph.numDateTicks=function(e,b,f){if(f=Dygraph.SHORT_SPACINGS[Dygraph.TWO_HOURLY]);for(m=p;m<=l;m+=c){A=new Date(m);if(e&&A.getTimezoneOffset()!=B){var k=A.getTimezoneOffset()-B;m+=k*60*1000;A=new Date(m);B=A.getTimezoneOffset();if(new Date(m+c).getTimezoneOffset()!=B){m+=c;A=new Date(m);B=A.getTimezoneOffset()}}C.push({v:m,label:w(A,a,n,z)})}}else{var f;var r=1;if(al){continue}C.push({v:m,label:w(new Date(m),a,n,z)})}}}return C};if(Dygraph&&Dygraph.DEFAULT_ATTRS&&Dygraph.DEFAULT_ATTRS.axes&&Dygraph.DEFAULT_ATTRS.axes["x"]&&Dygraph.DEFAULT_ATTRS.axes["y"]&&Dygraph.DEFAULT_ATTRS.axes["y2"]){Dygraph.DEFAULT_ATTRS.axes["x"]["ticker"]=Dygraph.dateTicker;Dygraph.DEFAULT_ATTRS.axes["y"]["ticker"]=Dygraph.numericTicks;Dygraph.DEFAULT_ATTRS.axes["y2"]["ticker"]=Dygraph.numericTicks}Dygraph.Plugins={};Dygraph.Plugins.Annotations=(function(){var a=function(){this.annotations_=[]};a.prototype.toString=function(){return"Annotations Plugin"};a.prototype.activate=function(b){return{clearChart:this.clearChart,didDrawChart:this.didDrawChart}};a.prototype.detachLabels=function(){for(var c=0;cu.x+u.w||l.canvasyu.y+u.h){continue}var w=l.annotation;var n=6;if(w.hasOwnProperty("tickHeight")){n=w.tickHeight}var j=document.createElement("div");for(var A in x){if(x.hasOwnProperty(A)){j.style[A]=x[A]}}if(!w.hasOwnProperty("icon")){j.className="dygraphDefaultAnnotation"}if(w.hasOwnProperty("cssClass")){j.className+=" "+w.cssClass}var m=w.hasOwnProperty("width")?w.width:16;var k=w.hasOwnProperty("height")?w.height:16;if(w.hasOwnProperty("icon")){var z=document.createElement("img");z.src=w.icon;z.width=m;z.height=k;j.appendChild(z)}else{if(l.annotation.hasOwnProperty("shortText")){j.appendChild(document.createTextNode(l.annotation.shortText))}}var c=l.canvasx-m/2;j.style.left=c+"px";var f=0;if(w.attachAtBottom){var d=(u.y+u.h-k-n);if(q[c]){d-=q[c]}else{q[c]=0}q[c]+=(n+k);f=d}else{f=l.canvasy-k-n}j.style.top=f+"px";j.style.width=m+"px";j.style.height=k+"px";j.title=l.annotation.text;j.style.color=t.colorsMap_[l.name];j.style.borderColor=t.colorsMap_[l.name];w.div=j;t.addAndTrackEvent(j,"click",b("clickHandler","annotationClickHandler",l,this));t.addAndTrackEvent(j,"mouseover",b("mouseOverHandler","annotationMouseOverHandler",l,this));t.addAndTrackEvent(j,"mouseout",b("mouseOutHandler","annotationMouseOutHandler",l,this));t.addAndTrackEvent(j,"dblclick",b("dblClickHandler","annotationDblClickHandler",l,this));h.appendChild(j);this.annotations_.push(j);var o=v.drawingContext;o.save();o.strokeStyle=t.colorsMap_[l.name];o.beginPath();if(!w.attachAtBottom){o.moveTo(l.canvasx,l.canvasy);o.lineTo(l.canvasx,l.canvasy-2-n)}else{var d=f+k;o.moveTo(l.canvasx,d);o.lineTo(l.canvasx,d+n)}o.closePath();o.stroke();o.restore()}};a.prototype.destroy=function(){this.detachLabels()};return a})();Dygraph.Plugins.Axes=(function(){var a=function(){this.xlabels_=[];this.ylabels_=[]};a.prototype.toString=function(){return"Axes Plugin"};a.prototype.activate=function(b){return{layout:this.layout,clearChart:this.clearChart,willDrawChart:this.willDrawChart}};a.prototype.layout=function(f){var d=f.dygraph;if(d.getOption("drawYAxis")){var b=d.getOption("yAxisLabelWidth")+2*d.getOption("axisTickSize");f.reserveSpaceLeft(b)}if(d.getOption("drawXAxis")){var c;if(d.getOption("xAxisHeight")){c=d.getOption("xAxisHeight")}else{c=d.getOptionForAxis("axisLabelFontSize","x")+2*d.getOption("axisTickSize")}f.reserveSpaceBottom(c)}if(d.numAxes()==2){if(d.getOption("drawYAxis")){var b=d.getOption("yAxisLabelWidth")+2*d.getOption("axisTickSize");f.reserveSpaceRight(b)}}else{if(d.numAxes()>2){d.error("Only two y-axes are supported at this time. (Trying to use "+d.numAxes()+")")}}};a.prototype.detachLabels=function(){function b(d){for(var c=0;c0){var h=F.numAxes();for(D=0;Dd){s.style.bottom="0px"}else{s.style.top=z+"px"}if(E[0]===0){s.style.left=(G.x-F.getOption("yAxisLabelWidth")-F.getOption("axisTickSize"))+"px";s.style.textAlign="right"}else{if(E[0]==1){s.style.left=(G.x+G.w+F.getOption("axisTickSize"))+"px";s.style.textAlign="left"}}s.style.width=F.getOption("yAxisLabelWidth")+"px";v.appendChild(s);this.ylabels_.push(s)}var n=this.ylabels_[0];var k=F.getOptionForAxis("axisLabelFontSize","y");var q=parseInt(n.style.top,10)+k;if(q>d-k){n.style.top=(parseInt(n.style.top,10)-k/2)+"px"}}var c;if(F.getOption("drawAxesAtZero")){var w=F.toPercentXCoord(0);if(w>1||w<0||isNaN(w)){w=0}c=B(G.x+w*G.w)}else{c=B(G.x)}j.strokeStyle=F.getOptionForAxis("axisLineColor","y");j.lineWidth=F.getOptionForAxis("axisLineWidth","y");j.beginPath();j.moveTo(c,A(G.y));j.lineTo(c,A(G.y+G.h));j.closePath();j.stroke();if(F.numAxes()==2){j.strokeStyle=F.getOptionForAxis("axisLineColor","y2");j.lineWidth=F.getOptionForAxis("axisLineWidth","y2");j.beginPath();j.moveTo(A(G.x+G.w),A(G.y));j.lineTo(A(G.x+G.w),A(G.y+G.h));j.closePath();j.stroke()}}if(F.getOption("drawXAxis")){if(I.xticks){for(D=0;DJ){l=J-F.getOption("xAxisLabelWidth");s.style.textAlign="right"}if(l<0){l=0;s.style.textAlign="left"}s.style.left=l+"px";s.style.width=F.getOption("xAxisLabelWidth")+"px";v.appendChild(s);this.xlabels_.push(s)}}j.strokeStyle=F.getOptionForAxis("axisLineColor","x");j.lineWidth=F.getOptionForAxis("axisLineWidth","x");j.beginPath();var b;if(F.getOption("drawAxesAtZero")){var w=F.toPercentYCoord(0,0);if(w>1||w<0){w=1}b=A(G.y+w*G.h)}else{b=A(G.y+G.h)}j.moveTo(B(G.x),b);j.lineTo(B(G.x+G.w),b);j.closePath();j.stroke()}j.restore()};return a})();Dygraph.Plugins.ChartLabels=(function(){var c=function(){this.title_div_=null;this.xlabel_div_=null;this.ylabel_div_=null;this.y2label_div_=null};c.prototype.toString=function(){return"ChartLabels Plugin"};c.prototype.activate=function(d){return{layout:this.layout,didDrawChart:this.didDrawChart}};var b=function(d){var e=document.createElement("div");e.style.position="absolute";e.style.left=d.x+"px";e.style.top=d.y+"px";e.style.width=d.w+"px";e.style.height=d.h+"px";return e};c.prototype.detachLabels_=function(){var e=[this.title_div_,this.xlabel_div_,this.ylabel_div_,this.y2label_div_];for(var d=0;d=2)}}u=t.yticks;l.save();for(p=0;p=2);if(n){l.installPattern(d)}l.strokeStyle=q.getOptionForAxis("gridLineColor","x");l.lineWidth=q.getOptionForAxis("gridLineWidth","x");for(p=0;p":" ")}m=w.getOption("strokePattern",z[u]);s=d(m,q.color,f);r+=""+s+" "+z[u]+""}return r}var A=w.optionsViewForAxis_("x");var o=A("valueFormatter");r=o(p,A,z[0],w);if(r!==""){r+=":"}var v=[];var j=w.numAxes();for(u=0;u"}var q=w.getPropertiesForSeries(t.name);var n=v[q.axis-1];var y=n("valueFormatter");var e=y(t.yval,n,t.name,w);var h=(t.name==B)?" class='highlight'":"";r+=" "+t.name+": "+e+""}return r};d=function(s,h,r){var e=(/MSIE/.test(navigator.userAgent)&&!window.opera);if(e){return"—"}if(!s||s.length<=1){return'
'}var l,k,f,o;var g=0,q=0;var p=[];var n;for(l=0;l<=s.length;l++){g+=s[l%s.length]}n=Math.floor(r/(g-s[0]));if(n>1){for(l=0;l'}}return m};return c})();Dygraph.Plugins.RangeSelector=(function(){var a=function(){this.isIE_=/MSIE/.test(navigator.userAgent)&&!window.opera;this.hasTouchInterface_=typeof(TouchEvent)!="undefined";this.isMobileDevice_=/mobile|android/gi.test(navigator.appVersion);this.interfaceCreated_=false};a.prototype.toString=function(){return"RangeSelector Plugin"};a.prototype.activate=function(b){this.dygraph_=b;this.isUsingExcanvas_=b.isUsingExcanvas_;if(this.getOption_("showRangeSelector")){this.createInterface_()}return{layout:this.reserveSpace_,predraw:this.renderStaticLayer_,didDrawChart:this.renderInteractiveLayer_}};a.prototype.destroy=function(){this.bgcanvas_=null;this.fgcanvas_=null;this.leftZoomHandle_=null;this.rightZoomHandle_=null;this.iePanOverlay_=null};a.prototype.getOption_=function(b){return this.dygraph_.getOption(b)};a.prototype.setDefaultOption_=function(b,c){return this.dygraph_.attrs_[b]=c};a.prototype.createInterface_=function(){this.createCanvases_();if(this.isUsingExcanvas_){this.createIEPanOverlay_()}this.createZoomHandles_();this.initInteraction_();if(this.getOption_("animatedZooms")){this.dygraph_.warn("Animated zooms and range selector are not compatible; disabling animatedZooms.");this.dygraph_.updateOptions({animatedZooms:false},true)}this.interfaceCreated_=true;this.addToGraph_()};a.prototype.addToGraph_=function(){var b=this.graphDiv_=this.dygraph_.graphDiv;b.appendChild(this.bgcanvas_);b.appendChild(this.fgcanvas_);b.appendChild(this.leftZoomHandle_);b.appendChild(this.rightZoomHandle_)};a.prototype.removeFromGraph_=function(){var b=this.graphDiv_;b.removeChild(this.bgcanvas_);b.removeChild(this.fgcanvas_);b.removeChild(this.leftZoomHandle_);b.removeChild(this.rightZoomHandle_);this.graphDiv_=null};a.prototype.reserveSpace_=function(b){if(this.getOption_("showRangeSelector")){b.reserveSpaceBottom(this.getOption_("rangeSelectorHeight")+4)}};a.prototype.renderStaticLayer_=function(){if(!this.updateVisibility_()){return}this.resize_();this.drawStaticLayer_()};a.prototype.renderInteractiveLayer_=function(){if(!this.updateVisibility_()||this.isChangingRange_){return}this.placeZoomHandles_();this.drawInteractiveLayer_()};a.prototype.updateVisibility_=function(){var b=this.getOption_("showRangeSelector");if(b){if(!this.interfaceCreated_){this.createInterface_()}else{if(!this.graphDiv_||!this.graphDiv_.parentNode){this.addToGraph_()}}}else{if(this.graphDiv_){this.removeFromGraph_();var c=this.dygraph_;setTimeout(function(){c.width_=0;c.resize()},1)}}return b};a.prototype.resize_=function(){function d(e,f){e.style.top=f.y+"px";e.style.left=f.x+"px";e.width=f.w;e.height=f.h;e.style.width=e.width+"px";e.style.height=e.height+"px"}var c=this.dygraph_.layout_.getPlotArea();var b=0;if(this.getOption_("drawXAxis")){b=this.getOption_("xAxisHeight")||(this.getOption_("axisLabelFontSize")+2*this.getOption_("axisTickSize"))}this.canvasRect_={x:c.x,y:c.y+c.h+b+4,w:c.w,h:this.getOption_("rangeSelectorHeight")};d(this.bgcanvas_,this.canvasRect_);d(this.fgcanvas_,this.canvasRect_)};a.prototype.createCanvases_=function(){this.bgcanvas_=Dygraph.createCanvas();this.bgcanvas_.className="dygraph-rangesel-bgcanvas";this.bgcanvas_.style.position="absolute";this.bgcanvas_.style.zIndex=9;this.bgcanvas_ctx_=Dygraph.getContext(this.bgcanvas_);this.fgcanvas_=Dygraph.createCanvas();this.fgcanvas_.className="dygraph-rangesel-fgcanvas";this.fgcanvas_.style.position="absolute";this.fgcanvas_.style.zIndex=9;this.fgcanvas_.style.cursor="default";this.fgcanvas_ctx_=Dygraph.getContext(this.fgcanvas_)};a.prototype.createIEPanOverlay_=function(){this.iePanOverlay_=document.createElement("div");this.iePanOverlay_.style.position="absolute";this.iePanOverlay_.style.backgroundColor="white";this.iePanOverlay_.style.filter="alpha(opacity=0)";this.iePanOverlay_.style.display="none";this.iePanOverlay_.style.cursor="move";this.fgcanvas_.appendChild(this.iePanOverlay_)};a.prototype.createZoomHandles_=function(){var b=new Image();b.className="dygraph-rangesel-zoomhandle";b.style.position="absolute";b.style.zIndex=10;b.style.visibility="hidden";b.style.cursor="col-resize";if(/MSIE 7/.test(navigator.userAgent)){b.width=7;b.height=14;b.style.backgroundColor="white";b.style.border="1px solid #333333"}else{b.width=9;b.height=16;b.src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAQCAYAAADESFVDAAAAAXNSR0IArs4c6QAAAAZiS0dEANAAzwDP4Z7KegAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB9sHGw0cMqdt1UwAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAaElEQVQoz+3SsRFAQBCF4Z9WJM8KCDVwownl6YXsTmCUsyKGkZzcl7zkz3YLkypgAnreFmDEpHkIwVOMfpdi9CEEN2nGpFdwD03yEqDtOgCaun7sqSTDH32I1pQA2Pb9sZecAxc5r3IAb21d6878xsAAAAAASUVORK5CYII="}if(this.isMobileDevice_){b.width*=2;b.height*=2}this.leftZoomHandle_=b;this.rightZoomHandle_=b.cloneNode(false)};a.prototype.initInteraction_=function(){var o=this;var i=this.isIE_?document:window;var u=0;var v=null;var s=false;var d=false;var g=!this.isMobileDevice_&&!this.isUsingExcanvas_;var k=new Dygraph.IFrameTarp();var p,f,r,j,w,h,x,t,q,c,l;var e,n,m;p=function(C){var B=o.dygraph_.xAxisExtremes();var z=(B[1]-B[0])/o.canvasRect_.w;var A=B[0]+(C.leftHandlePos-o.canvasRect_.x)*z;var y=B[0]+(C.rightHandlePos-o.canvasRect_.x)*z;return[A,y]};f=function(y){Dygraph.cancelEvent(y);s=true;u=y.clientX;v=y.target?y.target:y.srcElement;if(y.type==="mousedown"||y.type==="dragstart"){Dygraph.addEvent(i,"mousemove",r);Dygraph.addEvent(i,"mouseup",j)}o.fgcanvas_.style.cursor="col-resize";k.cover();return true};r=function(C){if(!s){return false}Dygraph.cancelEvent(C);var z=C.clientX-u;if(Math.abs(z)<4){return true}u=C.clientX;var B=o.getZoomHandleStatus_();var y;if(v==o.leftZoomHandle_){y=B.leftHandlePos+z;y=Math.min(y,B.rightHandlePos-v.width-3);y=Math.max(y,o.canvasRect_.x)}else{y=B.rightHandlePos+z;y=Math.min(y,o.canvasRect_.x+o.canvasRect_.w);y=Math.max(y,B.leftHandlePos+v.width+3)}var A=v.width/2;v.style.left=(y-A)+"px";o.drawInteractiveLayer_();if(g){w()}return true};j=function(y){if(!s){return false}s=false;k.uncover();Dygraph.removeEvent(i,"mousemove",r);Dygraph.removeEvent(i,"mouseup",j);o.fgcanvas_.style.cursor="default";if(!g){w()}return true};w=function(){try{var z=o.getZoomHandleStatus_();o.isChangingRange_=true;if(!z.isZoomed){o.dygraph_.resetZoom()}else{var y=p(z);o.dygraph_.doZoomXDates_(y[0],y[1])}}finally{o.isChangingRange_=false}};h=function(A){if(o.isUsingExcanvas_){return A.srcElement==o.iePanOverlay_}else{var z=o.leftZoomHandle_.getBoundingClientRect();var y=z.left+z.width/2;z=o.rightZoomHandle_.getBoundingClientRect();var B=z.left+z.width/2;return(A.clientX>y&&A.clientX=o.canvasRect_.x+o.canvasRect_.w){y=o.canvasRect_.x+o.canvasRect_.w;E=y-D}else{E+=z;y+=z}}var A=o.leftZoomHandle_.width/2;o.leftZoomHandle_.style.left=(E-A)+"px";o.rightZoomHandle_.style.left=(y-A)+"px";o.drawInteractiveLayer_();if(g){c()}return true};q=function(y){if(!d){return false}d=false;Dygraph.removeEvent(i,"mousemove",t);Dygraph.removeEvent(i,"mouseup",q);if(!g){c()}return true};c=function(){try{o.isChangingRange_=true;o.dygraph_.dateWindow_=p(o.getZoomHandleStatus_());o.dygraph_.drawGraph_(false)}finally{o.isChangingRange_=false}};l=function(y){if(s||d){return}var z=h(y)?"move":"default";if(z!=o.fgcanvas_.style.cursor){o.fgcanvas_.style.cursor=z}};e=function(y){if(y.type=="touchstart"&&y.targetTouches.length==1){if(f(y.targetTouches[0])){Dygraph.cancelEvent(y)}}else{if(y.type=="touchmove"&&y.targetTouches.length==1){if(r(y.targetTouches[0])){Dygraph.cancelEvent(y)}}else{j(y)}}};n=function(y){if(y.type=="touchstart"&&y.targetTouches.length==1){if(x(y.targetTouches[0])){Dygraph.cancelEvent(y)}}else{if(y.type=="touchmove"&&y.targetTouches.length==1){if(t(y.targetTouches[0])){Dygraph.cancelEvent(y)}}else{q(y)}}};m=function(B,A){var z=["touchstart","touchend","touchmove","touchcancel"];for(var y=0;y1&&v[t][1]!==null){m=typeof v[t][1]!="number";if(m){d=[];h=[];for(r=0;r0)){b=Math.min(b,g);c=Math.max(c,g)}}var o=0.25;if(u){c=Dygraph.log10(c);c+=c*o;b=Dygraph.log10(b);for(t=0;tthis.canvasRect_.x||b+1 - - - Sfive Stats - - - - - - -
-

Sfive Stat Display

- -
Loading Data ...
-
-
-
- - diff --git a/src/viz/index.js b/src/viz/index.js deleted file mode 100644 index ee51064..0000000 --- a/src/viz/index.js +++ /dev/null @@ -1,173 +0,0 @@ -var qintv_ms = 5000; -var uripart = "/updates?"; -//var lastfrom = ""; -var lastfrom="limit=12000"; -//var lastfrom = "from="+(new Date(Date.now() - 10*60*1000)).toISOString(); -//var lastfrom = "from="+(new Date(Date.now() - 10*60*1000)).toISOString()+"&limit=1000"; -//console.log(lastfrom); -//var possible_display_functions = {"Off":"none", "Value":showvalue, "Gauge":showgauge, "Graph":showasgraph}; -var possible_display_functions = {"Off":"none", "Value":showvalue, "Graph":showasgraph}; -var stuff_to_display = {"clients":"none","*":showvalue,"client-count":showasgraph, "bytes-sent":showasgraph}; -var sidseen = []; - -$(document).ready(function() -{ - updateS5Stats(); - setInterval("updateS5Stats()", qintv_ms); -}); - -function getstreamdiv(sid) { - var elem = $("#"+sid); - if (elem.length<1) { - $("#content").append('

Stream: '+sid+'

    '); - $("#menu").append(''+sid+' '); - elem = $("#"+sid); - } - return elem; -} - -function killvalue(name, nosetselect) { - stuff_to_display[name]="none"; - $.each(sidseen,function(index,sid){ $("#li-"+sid+"_"+name).remove() }); - if (nosetselect != 1) { - document.getElementById("opts-"+name).selectedIndex=0; - } -} - -function showvalue(sid, name) { - var divid = sid+"_"+name; - var elem = $("#"+divid); - if (elem.length<1) { - getstreamdiv(sid).append('
  • '+ name +'
  • '); - elem = $("#"+divid); - } - var jsdvalue = graphdata[divid][graphdata[divid].length-1]; - var jsdate = jsdvalue[0]; - var value = jsdvalue[1]; - elem.html(""+value+"
    @"+jsdate.toLocaleDateString()+" "+jsdate.toLocaleTimeString()+""); -} - -var gauges = {}; -function showgauge(sid, name) { - var divid = sid+"_"+name; - var elem = document.getElementById(divid); - if (!elem) { - getstreamdiv(sid).append('
  • '+ name +'
  • '); - elem = document.getElementById(divid); - gauges[divid] = new google.visualization.Gauge(elem); - } - var value = graphdata[divid][graphdata[divid].length-1][1]; - var data = google.visualization.arrayToDataTable([["Label", "Value"],[name,value]]); - gauges[divid].draw(data, {}); -} - -var graphs = {} -function showasgraph(sid, name) { - var divid = sid+"_"+name; - var elem = document.getElementById(divid); - if (!elem) { - getstreamdiv(sid).append('
  • '+ name +'
  • '); - elem = document.getElementById(divid); - graphs[divid] = new Dygraph(elem,graphdata[divid],{legend:'none',title:sid+"-"+name,labels:["Date/Time",name]}); - } else { - graphs[divid].updateOptions({'file':graphdata[divid]}); - } -} - -var graphdata = {} -function savedata(sid, name, value, jsdate) { - var divid = sid+"_"+name; - if (!graphdata[divid]) { - graphdata[divid]=[]; - } - graphdata[divid].push([jsdate,value]); -} - -function showasetting(name) { - var setid = "opts-"+name; - if (! document.getElementById(setid)) { - var sts = $('",a.querySelectorAll("[msallowclip^='']").length&&q.push("[*^$]="+M+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+M+"*(?:value|"+L+")"),a.querySelectorAll(":checked").length||q.push(":checked")}),ib(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+M+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ib(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",Q)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===v&&t(v,a)?-1:b===e||b.ownerDocument===v&&t(v,b)?1:k?K.call(k,a)-K.call(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],i=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:k?K.call(k,a)-K.call(k,b):0;if(f===g)return kb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?kb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},e):n},fb.matches=function(a,b){return fb(a,null,null,b)},fb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fb(b,n,null,[a]).length>0},fb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&E.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fb.selectors={cacheLength:50,createPseudo:hb,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+M+")"+a+"("+M+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==C&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?hb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=K.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:hb(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?hb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:hb(function(a){return function(b){return fb(a,b).length>0}}),contains:hb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:hb(function(a){return W.test(a||"")||fb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:nb(function(){return[0]}),last:nb(function(a,b){return[b-1]}),eq:nb(function(a,b,c){return[0>c?c+b:c]}),even:nb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:nb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:nb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:nb(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function rb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function sb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function tb(a,b,c){for(var d=0,e=b.length;e>d;d++)fb(a,b[d],c);return c}function ub(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function vb(a,b,c,d,e,f){return d&&!d[u]&&(d=vb(d)),e&&!e[u]&&(e=vb(e,f)),hb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||tb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ub(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ub(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?K.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ub(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):I.apply(g,r)})}function wb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=rb(function(a){return a===b},h,!0),l=rb(function(a){return K.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>i;i++)if(c=d.relative[a[i].type])m=[rb(sb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return vb(i>1&&sb(m),i>1&&qb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&wb(a.slice(i,e)),f>e&&wb(a=a.slice(e)),f>e&&qb(a))}m.push(c)}return sb(m)}function xb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=G.call(i));s=ub(s)}I.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&fb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?hb(f):f}return h=fb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xb(e,d)),f.selector=a}return f},i=fb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&ob(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qb(j),!a)return I.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&ob(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ib(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ib(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||jb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ib(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||jb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ib(function(a){return null==a.getAttribute("disabled")})||jb(L,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fb}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+Math.random()}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b) -},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthx",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,bb=/<([\w:]+)/,cb=/<|&#?\w+;/,db=/<(?:script|style|link)/i,eb=/checked\s*(?:[^=]|=\s*.checked.)/i,fb=/^$|\/(?:java|ecma)script/i,gb=/^true\/(.*)/,hb=/^\s*\s*$/g,ib={option:[1,""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};ib.optgroup=ib.option,ib.tbody=ib.tfoot=ib.colgroup=ib.caption=ib.thead,ib.th=ib.td;function jb(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function kb(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function lb(a){var b=gb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function mb(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function nb(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function ob(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pb(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=ob(h),f=ob(a),d=0,e=f.length;e>d;d++)pb(f[d],g[d]);if(b)if(c)for(f=f||ob(a),g=g||ob(h),d=0,e=f.length;e>d;d++)nb(f[d],g[d]);else nb(a,h);return g=ob(h,"script"),g.length>0&&mb(g,!i&&ob(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(cb.test(e)){f=f||k.appendChild(b.createElement("div")),g=(bb.exec(e)||["",""])[1].toLowerCase(),h=ib[g]||ib._default,f.innerHTML=h[1]+e.replace(ab,"<$1>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=ob(k.appendChild(e),"script"),i&&mb(f),c)){j=0;while(e=f[j++])fb.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(ob(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&mb(ob(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(ob(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!db.test(a)&&!ib[(bb.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(ab,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(ob(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(ob(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&eb.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(ob(c,"script"),kb),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,ob(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,lb),j=0;g>j;j++)h=f[j],fb.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(hb,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qb,rb={};function sb(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function tb(a){var b=l,c=rb[a];return c||(c=sb(a,b),"none"!==c&&c||(qb=(qb||n("