From 17f85ceccfa1bc40cce73359ce3961f2d94f61a9 Mon Sep 17 00:00:00 2001 From: Azareal Date: Sat, 17 Nov 2018 12:36:02 +1000 Subject: [PATCH] The time it took for the route to be processed is shown in the footer in the Nox Theme for admins now Don't waste queries on groups without any users for the topic list. Bypass the GzipResponseWriter in RunThemeTemplate, so we get indirected through fewer layers of Write calls. Added an experimental flag for disabling fsnotify. Refactored the topic benchmarks to make them a little more flexible. Refactored a couple of benchmarks to reduce the amount of boilerplate. Fixed a bug in the forum list for Nox where topic titles broke onto multiple lines. Refactored the template transpiler to reduce the amount of boilerplate for generating template functions. --- common/forum_perms.go | 14 +-- common/group_store.go | 12 +-- common/pages.go | 1 + common/routes_common.go | 10 ++ common/site.go | 1 + common/template_init.go | 21 ++-- common/templates/templates.go | 85 +++++++--------- common/theme_list.go | 15 +++ common/topic_list.go | 63 +++++------- gen_router.go | 18 +--- general_test.go | 77 +++------------ main.go | 142 +++++++++++++++------------ router_gen/main.go | 14 +-- router_gen/route_impl.go | 2 +- templates/footer.html | 1 + themes/cosora/public/main.css | 3 + themes/nox/public/main.css | 16 ++- themes/shadow/public/main.css | 3 + themes/tempra-simple/public/main.css | 3 + 19 files changed, 229 insertions(+), 272 deletions(-) diff --git a/common/forum_perms.go b/common/forum_perms.go index f5486d4d..e6107fe2 100644 --- a/common/forum_perms.go +++ b/common/forum_perms.go @@ -174,11 +174,8 @@ func PermmapToQuery(permmap map[string]*ForumPerms, fid int) error { if err != nil { return err } - err = FPStore.Reload(fid) - if err != nil { - return err - } - return TopicList.RebuildPermTree() + return FPStore.Reload(fid) + //return TopicList.RebuildPermTree() } // TODO: FPStore.Reload? @@ -193,11 +190,8 @@ func ReplaceForumPermsForGroup(gid int, presetSet map[int]string, permSets map[i if err != nil { return err } - err = tx.Commit() - if err != nil { - return err - } - return TopicList.RebuildPermTree() + return tx.Commit() + //return TopicList.RebuildPermTree() } func ReplaceForumPermsForGroupTx(tx *sql.Tx, gid int, presetSets map[int]string, permSets map[int]*ForumPerms) error { diff --git a/common/group_store.go b/common/group_store.go index bee00cfe..582599ba 100644 --- a/common/group_store.go +++ b/common/group_store.go @@ -280,16 +280,8 @@ func (mgs *MemoryGroupStore) Create(name string, tag string, isAdmin bool, isMod mgs.groupCount++ mgs.Unlock() - err = FPStore.ReloadAll() - if err != nil { - return gid, err - } - err = TopicList.RebuildPermTree() - if err != nil { - return gid, err - } - - return gid, nil + return gid, FPStore.ReloadAll() + //return gid, TopicList.RebuildPermTree() } func (mgs *MemoryGroupStore) GetAll() (results []*Group, err error) { diff --git a/common/pages.go b/common/pages.go index c771caa2..354e7472 100644 --- a/common/pages.go +++ b/common/pages.go @@ -29,6 +29,7 @@ type Header struct { Zone string Path string MetaDesc string + StartedAt time.Time Writer http.ResponseWriter ExtData ExtData } diff --git a/common/routes_common.go b/common/routes_common.go index bd524619..bd057a15 100644 --- a/common/routes_common.go +++ b/common/routes_common.go @@ -6,6 +6,7 @@ import ( "net/http" "strconv" "strings" + "time" ) // nolint @@ -115,6 +116,10 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (header Writer: w, } // TODO: We should probably initialise header.ExtData + // ? - Should we only show this in debug mode? It might be useful for detecting issues in production, if we show it there as-well + if user.IsAdmin { + header.StartedAt = time.Now() + } header.AddSheet(theme.Name + "/panel.css") if len(theme.Resources) > 0 { @@ -193,6 +198,11 @@ func userCheck(w http.ResponseWriter, r *http.Request, user *User) (header *Head if user.Loggedin && !user.Active { header.AddNotice("account_inactive") } + // An optimisation so we don't populate StartedAt for users who shouldn't see the stat anyway + // ? - Should we only show this in debug mode? It might be useful for detecting issues in production, if we show it there as-well + if user.IsAdmin { + header.StartedAt = time.Now() + } if len(theme.Resources) > 0 { rlist := theme.Resources diff --git a/common/site.go b/common/site.go index 2258328e..5fdc7489 100644 --- a/common/site.go +++ b/common/site.go @@ -96,6 +96,7 @@ type devConfig struct { TemplateDebug bool Profiling bool TestDB bool + NoFsnotify bool // Super Experimental! } // configHolder is purely for having a big struct to unmarshal data into diff --git a/common/template_init.go b/common/template_init.go index 77f702f3..a2ee55c2 100644 --- a/common/template_init.go +++ b/common/template_init.go @@ -12,8 +12,8 @@ import ( "time" "github.com/Azareal/Gosora/common/alerts" - "github.com/Azareal/Gosora/common/templates" "github.com/Azareal/Gosora/common/phrases" + "github.com/Azareal/Gosora/common/templates" ) var Ctemplates []string @@ -162,15 +162,14 @@ func tmplInitHeaders(user User, user2 User, user3 User) (*Header, *Header, *Head }, } - var header2 = &Header{Site: Site} - *header2 = *header - header2.CurrentUser = user2 + buildHeader := func(user User) *Header { + var head = &Header{Site: Site} + *head = *header + head.CurrentUser = user + return head + } - var header3 = &Header{Site: Site} - *header3 = *header - header3.CurrentUser = user3 - - return header, header2, header3 + return header, buildHeader(user2), buildHeader(user3) } // ? - Add template hooks? @@ -527,6 +526,10 @@ func InitTemplates() error { return template.HTML(BuildWidget(dock.(string), headerInt.(*Header))) } + fmap["elapsed"] = func(startedAtInt interface{}) interface{} { + return time.Since(startedAtInt.(time.Time)).String() + } + fmap["lang"] = func(phraseNameInt interface{}) interface{} { phraseName, ok := phraseNameInt.(string) if !ok { diff --git a/common/templates/templates.go b/common/templates/templates.go index 8d547865..c8f71c71 100644 --- a/common/templates/templates.go +++ b/common/templates/templates.go @@ -14,6 +14,7 @@ import ( // TODO: Turn this file into a library var textOverlapList = make(map[string]int) + // TODO: Stop hard-coding this here var langPkg = "github.com/Azareal/Gosora/common/phrases" @@ -86,6 +87,7 @@ func NewCTemplateSet() *CTemplateSet { "multiply": true, "divide": true, "dock": true, + "elapsed": true, "lang": true, "level": true, "scope": true, @@ -626,6 +628,24 @@ func (c *CTemplateSet) compareJoin(varholder string, holdreflect reflect.Value, func (c *CTemplateSet) compileIdentSwitch(varholder string, holdreflect reflect.Value, templateName string, node *parse.CommandNode) (out string, val reflect.Value, literal bool) { c.detail("in compileIdentSwitch") + var litString = func(inner string) { + out = "w.Write([]byte(" + inner + "))\n" + literal = true + } + var compOpMappings = map[string]string{ + "le": "<=", + "lt": "<", + "gt": ">", + "ge": ">=", + "eq": "==", + "ne": "!=", + } + var mathOpMappings = map[string]string{ + "add": "+", + "subtract": "-", + "divide": "/", + "multiply": "*", + } ArgLoop: for pos := 0; pos < len(node.Args); pos++ { id := node.Args[pos] @@ -638,43 +658,21 @@ ArgLoop: var rout string pos, rout = c.compareJoin(varholder, holdreflect, templateName, pos, node, c.funcMap[id.String()].(string)) // TODO: Test this out += rout - case "le": // TODO: Can we condense these comparison cases down into one? - out += c.compareFunc(varholder, holdreflect, templateName, pos, node, "<=") + case "le", "lt", "gt", "ge", "eq", "ne": + out += c.compareFunc(varholder, holdreflect, templateName, pos, node, compOpMappings[id.String()]) break ArgLoop - case "lt": - out += c.compareFunc(varholder, holdreflect, templateName, pos, node, "<") - break ArgLoop - case "gt": - out += c.compareFunc(varholder, holdreflect, templateName, pos, node, ">") - break ArgLoop - case "ge": - out += c.compareFunc(varholder, holdreflect, templateName, pos, node, ">=") - break ArgLoop - case "eq": - out += c.compareFunc(varholder, holdreflect, templateName, pos, node, "==") - break ArgLoop - case "ne": - out += c.compareFunc(varholder, holdreflect, templateName, pos, node, "!=") - break ArgLoop - case "add": - rout, rval := c.simpleMath(varholder, holdreflect, templateName, pos, node, "+") + case "add", "subtract", "divide", "multiply": + rout, rval := c.simpleMath(varholder, holdreflect, templateName, pos, node, mathOpMappings[id.String()]) out += rout val = rval break ArgLoop - case "subtract": - rout, rval := c.simpleMath(varholder, holdreflect, templateName, pos, node, "-") - out += rout - val = rval - break ArgLoop - case "divide": - rout, rval := c.simpleMath(varholder, holdreflect, templateName, pos, node, "/") - out += rout - val = rval - break ArgLoop - case "multiply": - rout, rval := c.simpleMath(varholder, holdreflect, templateName, pos, node, "*") - out += rout - val = rval + case "elapsed": + leftOperand := node.Args[pos+1].String() + leftParam, _ := c.compileIfVarsub(leftOperand, varholder, templateName, holdreflect) + // TODO: Refactor this + // TODO: Validate that this is actually a time.Time + litString("time.Since(" + leftParam + ").String()") + c.importMap["time"] = "time" break ArgLoop case "dock": var leftParam, rightParam string @@ -701,40 +699,33 @@ ArgLoop: val = val3 // TODO: Refactor this - out = "w.Write([]byte(common.BuildWidget(" + leftParam + "," + rightParam + ")))\n" - literal = true + litString("common.BuildWidget(" + leftParam + "," + rightParam + ")") break ArgLoop case "lang": - var leftParam string // TODO: Implement string literals properly leftOperand := node.Args[pos+1].String() if len(leftOperand) == 0 { panic("The left operand for the language string cannot be left blank") } - - if leftOperand[0] == '"' { - // ! Slightly crude but it does the job - leftParam = strings.Replace(leftOperand, "\"", "", -1) - } else { + if leftOperand[0] != '"' { panic("Phrase names cannot be dynamic") } + // ! Slightly crude but it does the job + leftParam := strings.Replace(leftOperand, "\"", "", -1) c.langIndexToName = append(c.langIndexToName, leftParam) - out = "w.Write(plist[" + strconv.Itoa(len(c.langIndexToName)-1) + "])\n" - literal = true + litString("plist[" + strconv.Itoa(len(c.langIndexToName)-1) + "]") break ArgLoop case "level": - var leftParam string // TODO: Implement level literals leftOperand := node.Args[pos+1].String() if len(leftOperand) == 0 { panic("The leftoperand for function level cannot be left blank") } - leftParam, _ = c.compileIfVarsub(leftOperand, varholder, templateName, holdreflect) + leftParam, _ := c.compileIfVarsub(leftOperand, varholder, templateName, holdreflect) // TODO: Refactor this - out = "w.Write([]byte(phrases.GetLevelPhrase(" + leftParam + ")))\n" - literal = true + litString("phrases.GetLevelPhrase(" + leftParam + ")") c.importMap[langPkg] = langPkg break ArgLoop case "scope": diff --git a/common/theme_list.go b/common/theme_list.go index b527b70e..5ff499bc 100644 --- a/common/theme_list.go +++ b/common/theme_list.go @@ -289,10 +289,25 @@ func ResetTemplateOverrides() { log.Print("All of the template overrides have been reset") } +type GzipResponseWriter struct { + io.Writer + http.ResponseWriter +} + +func (w GzipResponseWriter) Write(b []byte) (int, error) { + return w.Writer.Write(b) +} + // NEW method of doing theme templates to allow one user to have a different theme to another. Under construction. // TODO: Generate the type switch instead of writing it by hand // TODO: Cut the number of types in half func RunThemeTemplate(theme string, template string, pi interface{}, w io.Writer) error { + // Unpack this to avoid an indirect call + gzw, ok := w.(GzipResponseWriter) + if ok { + w = gzw.Writer + } + var getTmpl = GetThemeTemplate(theme, template) switch tmplO := getTmpl.(type) { case *func(CustomPagePage, io.Writer) error: diff --git a/common/topic_list.go b/common/topic_list.go index 0f557a4c..976ab361 100644 --- a/common/topic_list.go +++ b/common/topic_list.go @@ -3,7 +3,6 @@ package common import ( "strconv" "sync" - "sync/atomic" "github.com/Azareal/Gosora/query_gen" ) @@ -20,8 +19,6 @@ type TopicListInt interface { GetListByCanSee(canSee []int, page int, orderby string) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) GetListByGroup(group *Group, page int, orderby string) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) GetList(page int, orderby string) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) - - RebuildPermTree() error } type DefaultTopicList struct { @@ -31,7 +28,7 @@ type DefaultTopicList struct { oddLock sync.RWMutex evenLock sync.RWMutex - permTree atomic.Value // [string(canSee)]canSee + //permTree atomic.Value // [string(canSee)]canSee //permTree map[string][]int // [string(canSee)]canSee } @@ -44,11 +41,7 @@ func NewDefaultTopicList() (*DefaultTopicList, error) { evenGroups: make(map[int]*TopicListHolder), } - err := tList.RebuildPermTree() - if err != nil { - return nil, err - } - err = tList.Tick() + err := tList.Tick() if err != nil { return nil, err } @@ -70,19 +63,13 @@ func (tList *DefaultTopicList) Tick() error { } } - var canSeeHolders = make(map[string]*TopicListHolder) - for name, canSee := range tList.permTree.Load().(map[string][]int) { - topicList, forumList, paginator, err := tList.GetListByCanSee(canSee, 1, "") - if err != nil { - return err - } - canSeeHolders[name] = &TopicListHolder{topicList, forumList, paginator} - } - allGroups, err := Groups.GetAll() if err != nil { return err } + + var gidToCanSee = make(map[int]string) + var permTree = make(map[string][]int) // [string(canSee)]canSee for _, group := range allGroups { // ? - Move the user count check to instance initialisation? Might require more book-keeping, particularly when a user moves into a zero user group if group.UserCount == 0 && group.ID != GuestUser.Group { @@ -92,7 +79,23 @@ func (tList *DefaultTopicList) Tick() error { for i, item := range group.CanSee { canSee[i] = byte(item) } - addList(group.ID, canSeeHolders[string(canSee)]) + var canSeeInt = make([]int, len(canSee)) + copy(canSeeInt, group.CanSee) + sCanSee := string(canSee) + permTree[sCanSee] = canSeeInt + gidToCanSee[group.ID] = sCanSee + } + + var canSeeHolders = make(map[string]*TopicListHolder) + for name, canSee := range permTree { + topicList, forumList, paginator, err := tList.GetListByCanSee(canSee, 1, "") + if err != nil { + return err + } + canSeeHolders[name] = &TopicListHolder{topicList, forumList, paginator} + } + for gid, canSee := range gidToCanSee { + addList(gid, canSeeHolders[canSee]) } tList.oddLock.Lock() @@ -106,28 +109,6 @@ func (tList *DefaultTopicList) Tick() error { return nil } -func (tList *DefaultTopicList) RebuildPermTree() error { - // TODO: Do something more efficient than this - allGroups, err := Groups.GetAll() - if err != nil { - return err - } - - var permTree = make(map[string][]int) // [string(canSee)]canSee - for _, group := range allGroups { - var canSee = make([]byte, len(group.CanSee)) - for i, item := range group.CanSee { - canSee[i] = byte(item) - } - var canSeeInt = make([]int, len(canSee)) - copy(canSeeInt, group.CanSee) - permTree[string(canSee)] = canSeeInt - } - tList.permTree.Store(permTree) - - return nil -} - func (tList *DefaultTopicList) GetListByGroup(group *Group, page int, orderby string) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) { if page == 0 { page = 1 diff --git a/gen_router.go b/gen_router.go index bbaa0727..87fa38d0 100644 --- a/gen_router.go +++ b/gen_router.go @@ -10,7 +10,6 @@ import ( "sync" "sync/atomic" "errors" - "io" "os" "net/http" @@ -536,15 +535,6 @@ func init() { counters.SetReverseOSMapEnum(reverseOSMapEnum) } -type gzipResponseWriter struct { - io.Writer - http.ResponseWriter -} - -func (w gzipResponseWriter) Write(b []byte) (int, error) { - return w.Writer.Write(b) -} - type WriterIntercept struct { http.ResponseWriter } @@ -889,7 +879,7 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { gz.Close() } }() - w = gzipResponseWriter{Writer: gz, ResponseWriter: w} + w = common.GzipResponseWriter{Writer: gz, ResponseWriter: w} } ferr := r.routeSwitch(w, req, user, prefix, extraData) @@ -944,7 +934,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - gzw, ok := w.(gzipResponseWriter) + gzw, ok := w.(common.GzipResponseWriter) if ok { w = gzw.ResponseWriter w.Header().Del("Content-Type") @@ -1406,7 +1396,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c return err } - gzw, ok := w.(gzipResponseWriter) + gzw, ok := w.(common.GzipResponseWriter) if ok { w = gzw.ResponseWriter w.Header().Del("Content-Type") @@ -1982,7 +1972,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c if extraData == "" { return common.NotFound(w,req,nil) } - gzw, ok := w.(gzipResponseWriter) + gzw, ok := w.(common.GzipResponseWriter) if ok { w = gzw.ResponseWriter w.Header().Del("Content-Type") diff --git a/general_test.go b/general_test.go index 55bc9a38..ba627874 100644 --- a/general_test.go +++ b/general_test.go @@ -108,6 +108,8 @@ func init() { } } +const benchTid = "1" + // TODO: Swap out LocalError for a panic for this? func BenchmarkTopicAdminRouteParallel(b *testing.B) { binit(b) @@ -128,7 +130,7 @@ func BenchmarkTopicAdminRouteParallel(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { w := httptest.NewRecorder() - reqAdmin := httptest.NewRequest("get", "/topic/hm.1", bytes.NewReader(nil)) + reqAdmin := httptest.NewRequest("get", "/topic/hm."+benchTid, bytes.NewReader(nil)) reqAdmin.AddCookie(&adminUIDCookie) reqAdmin.AddCookie(&adminSessionCookie) @@ -176,7 +178,7 @@ func BenchmarkTopicAdminRouteParallelWithRouter(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { w := httptest.NewRecorder() - reqAdmin := httptest.NewRequest("get", "/topic/hm.1", bytes.NewReader(nil)) + reqAdmin := httptest.NewRequest("get", "/topic/hm."+benchTid, bytes.NewReader(nil)) reqAdmin.AddCookie(&uidCookie) reqAdmin.AddCookie(&sessionCookie) reqAdmin.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36") @@ -215,7 +217,7 @@ func BenchmarkTopicGuestRouteParallel(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { w := httptest.NewRecorder() - req := httptest.NewRequest("get", "/topic/hm.1", bytes.NewReader(nil)) + req := httptest.NewRequest("get", "/topic/hm."+benchTid, bytes.NewReader(nil)) user := common.GuestUser head, err := common.UserCheck(w, req, &user) @@ -242,7 +244,7 @@ func BenchmarkTopicGuestRouteParallelDebugMode(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { w := httptest.NewRecorder() - req := httptest.NewRequest("get", "/topic/hm.1", bytes.NewReader(nil)) + req := httptest.NewRequest("get", "/topic/hm."+benchTid, bytes.NewReader(nil)) user := common.GuestUser head, err := common.UserCheck(w, req, &user) @@ -260,65 +262,6 @@ func BenchmarkTopicGuestRouteParallelDebugMode(b *testing.B) { cfg.Restore() } -func BenchmarkTopicGuestRouteParallelWithRouter(b *testing.B) { - binit(b) - router, err := NewGenRouter(http.FileServer(http.Dir("./uploads"))) - if err != nil { - b.Fatal(err) - } - cfg := NewStashConfig() - common.Dev.DebugMode = false - common.Dev.SuperDebug = false - - /*f, err := os.Create("BenchmarkTopicGuestRouteParallelWithRouter.prof") - if err != nil { - b.Fatal(err) - } - pprof.StartCPUProfile(f)*/ - - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - w := httptest.NewRecorder() - req := httptest.NewRequest("GET", "/topic/hm.1", bytes.NewReader(nil)) - req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36") - req.Header.Set("Host", "localhost") - req.Host = "localhost" - //w.Body.Reset() - router.ServeHTTP(w, req) - if w.Code != 200 { - b.Log(w.Body) - b.Fatal("HTTP Error!") - } - } - }) - - //defer pprof.StopCPUProfile() - cfg.Restore() -} - -func BenchmarkBadRouteGuestRouteParallelWithRouter(b *testing.B) { - binit(b) - router, err := NewGenRouter(http.FileServer(http.Dir("./uploads"))) - if err != nil { - b.Fatal(err) - } - cfg := NewStashConfig() - common.Dev.DebugMode = false - common.Dev.SuperDebug = false - - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - w := httptest.NewRecorder() - req := httptest.NewRequest("GET", "/garble/haa", bytes.NewReader(nil)) - req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36") - req.Header.Set("Host", "localhost") - req.Host = "localhost" - router.ServeHTTP(w, req) - } - }) - cfg.Restore() -} - func obRoute(b *testing.B, path string) { binit(b) cfg := NewStashConfig() @@ -340,6 +283,14 @@ func BenchmarkForumGuestRouteParallelWithRouter(b *testing.B) { obRoute(b, "/forum/general.2") } +func BenchmarkTopicGuestRouteParallelWithRouter(b *testing.B) { + obRoute(b, "/topic/hm."+benchTid) +} + +func BenchmarkBadRouteGuestRouteParallelWithRouter(b *testing.B) { + obRoute(b, "/garble/haa") +} + func binit(b *testing.B) { b.ReportAllocs() err := gloinit() diff --git a/main.go b/main.go index c9697126..a58bf91e 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,7 @@ import ( "net/http" "os" "os/signal" + "runtime" "runtime/pprof" "strings" "syscall" @@ -24,8 +25,8 @@ import ( "github.com/Azareal/Gosora/common" "github.com/Azareal/Gosora/common/counters" "github.com/Azareal/Gosora/common/phrases" - "github.com/Azareal/Gosora/routes" "github.com/Azareal/Gosora/query_gen" + "github.com/Azareal/Gosora/routes" "github.com/fsnotify/fsnotify" "github.com/pkg/errors" ) @@ -289,72 +290,74 @@ func main() { log.Fatal(err) } - log.Print("Initialising the file watcher") - watcher, err := fsnotify.NewWatcher() - if err != nil { - log.Fatal(err) - } - defer watcher.Close() - - go func() { - var modifiedFileEvent = func(path string) error { - var pathBits = strings.Split(path, "\\") - if len(pathBits) == 0 { - return nil - } - if pathBits[0] == "themes" { - var themeName string - if len(pathBits) >= 2 { - themeName = pathBits[1] - } - if len(pathBits) >= 3 && pathBits[2] == "public" { - // TODO: Handle new themes freshly plopped into the folder? - theme, ok := common.Themes[themeName] - if ok { - return theme.LoadStaticFiles() - } - } - } - return nil - } - - // TODO: Expand this to more types of files - var err error - for { - select { - case event := <-watcher.Events: - log.Println("event:", event) - // TODO: Handle file deletes (and renames more graciously by removing the old version of it) - if event.Op&fsnotify.Write == fsnotify.Write { - log.Println("modified file:", event.Name) - err = modifiedFileEvent(event.Name) - } else if event.Op&fsnotify.Create == fsnotify.Create { - log.Println("new file:", event.Name) - err = modifiedFileEvent(event.Name) - } - if err != nil { - common.LogError(err) - } - case err = <-watcher.Errors: - common.LogError(err) - } - } - }() - - // TODO: Keep tabs on the (non-resource) theme stuff, and the langpacks - err = watcher.Add("./public") - if err != nil { - log.Fatal(err) - } - err = watcher.Add("./templates") - if err != nil { - log.Fatal(err) - } - for _, theme := range common.Themes { - err = watcher.Add("./themes/" + theme.Name + "/public") + if !common.Dev.NoFsnotify { + log.Print("Initialising the file watcher") + watcher, err := fsnotify.NewWatcher() if err != nil { log.Fatal(err) } + defer watcher.Close() + + go func() { + var modifiedFileEvent = func(path string) error { + var pathBits = strings.Split(path, "\\") + if len(pathBits) == 0 { + return nil + } + if pathBits[0] == "themes" { + var themeName string + if len(pathBits) >= 2 { + themeName = pathBits[1] + } + if len(pathBits) >= 3 && pathBits[2] == "public" { + // TODO: Handle new themes freshly plopped into the folder? + theme, ok := common.Themes[themeName] + if ok { + return theme.LoadStaticFiles() + } + } + } + return nil + } + + // TODO: Expand this to more types of files + var err error + for { + select { + case event := <-watcher.Events: + log.Println("event:", event) + // TODO: Handle file deletes (and renames more graciously by removing the old version of it) + if event.Op&fsnotify.Write == fsnotify.Write { + log.Println("modified file:", event.Name) + err = modifiedFileEvent(event.Name) + } else if event.Op&fsnotify.Create == fsnotify.Create { + log.Println("new file:", event.Name) + err = modifiedFileEvent(event.Name) + } + if err != nil { + common.LogError(err) + } + case err = <-watcher.Errors: + common.LogError(err) + } + } + }() + + // TODO: Keep tabs on the (non-resource) theme stuff, and the langpacks + err = watcher.Add("./public") + if err != nil { + log.Fatal(err) + } + err = watcher.Add("./templates") + if err != nil { + log.Fatal(err) + } + for _, theme := range common.Themes { + err = watcher.Add("./themes/" + theme.Name + "/public") + if err != nil { + log.Fatal(err) + } + } } log.Print("Initialising the task system") @@ -437,6 +440,17 @@ func main() { args := <-common.StopServerChan if false { pprof.StopCPUProfile() + f, err := os.Create("./logs/mem.prof") + if err != nil { + log.Fatal(err) + } + defer f.Close() + + runtime.GC() + err = pprof.WriteHeapProfile(f) + if err != nil { + log.Fatal(err) + } } // Why did the server stop? log.Fatal(args...) diff --git a/router_gen/main.go b/router_gen/main.go index 74a63d3d..7ad282ea 100644 --- a/router_gen/main.go +++ b/router_gen/main.go @@ -239,7 +239,6 @@ import ( "sync" "sync/atomic" "errors" - "io" "os" "net/http" @@ -318,15 +317,6 @@ func init() { counters.SetReverseOSMapEnum(reverseOSMapEnum) } -type gzipResponseWriter struct { - io.Writer - http.ResponseWriter -} - -func (w gzipResponseWriter) Write(b []byte) (int, error) { - return w.Writer.Write(b) -} - type WriterIntercept struct { http.ResponseWriter } @@ -671,7 +661,7 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { gz.Close() } }() - w = gzipResponseWriter{Writer: gz, ResponseWriter: w} + w = common.GzipResponseWriter{Writer: gz, ResponseWriter: w} } ferr := r.routeSwitch(w, req, user, prefix, extraData) @@ -691,7 +681,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c if extraData == "" { return common.NotFound(w,req,nil) } - gzw, ok := w.(gzipResponseWriter) + gzw, ok := w.(common.GzipResponseWriter) if ok { w = gzw.ResponseWriter w.Header().Del("Content-Type") diff --git a/router_gen/route_impl.go b/router_gen/route_impl.go index 5c885335..694dbb4f 100644 --- a/router_gen/route_impl.go +++ b/router_gen/route_impl.go @@ -60,7 +60,7 @@ func (route *RouteImpl) hasBeforeItem(item string) bool { } func (route *RouteImpl) NoGzip() *RouteImpl { - return route.LitBeforeMultiline(`gzw, ok := w.(gzipResponseWriter) + return route.LitBeforeMultiline(`gzw, ok := w.(common.GzipResponseWriter) if ok { w = gzw.ResponseWriter w.Header().Del("Content-Type") diff --git a/templates/footer.html b/templates/footer.html index 850a4045..9d1ff504 100644 --- a/templates/footer.html +++ b/templates/footer.html @@ -9,6 +9,7 @@
{{lang "footer_powered_by"}} - {{lang "footer_made_with_love"}}
+ {{if .CurrentUser.IsAdmin}}
{{elapsed .Header.StartedAt}}
{{end}}