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.
This commit is contained in:
Azareal 2018-11-17 12:36:02 +10:00
parent 8e997048e3
commit 17f85ceccf
19 changed files with 229 additions and 272 deletions

View File

@ -174,11 +174,8 @@ func PermmapToQuery(permmap map[string]*ForumPerms, fid int) error {
if err != nil { if err != nil {
return err return err
} }
err = FPStore.Reload(fid) return FPStore.Reload(fid)
if err != nil { //return TopicList.RebuildPermTree()
return err
}
return TopicList.RebuildPermTree()
} }
// TODO: FPStore.Reload? // TODO: FPStore.Reload?
@ -193,11 +190,8 @@ func ReplaceForumPermsForGroup(gid int, presetSet map[int]string, permSets map[i
if err != nil { if err != nil {
return err return err
} }
err = tx.Commit() return tx.Commit()
if err != nil { //return TopicList.RebuildPermTree()
return err
}
return TopicList.RebuildPermTree()
} }
func ReplaceForumPermsForGroupTx(tx *sql.Tx, gid int, presetSets map[int]string, permSets map[int]*ForumPerms) error { func ReplaceForumPermsForGroupTx(tx *sql.Tx, gid int, presetSets map[int]string, permSets map[int]*ForumPerms) error {

View File

@ -280,16 +280,8 @@ func (mgs *MemoryGroupStore) Create(name string, tag string, isAdmin bool, isMod
mgs.groupCount++ mgs.groupCount++
mgs.Unlock() mgs.Unlock()
err = FPStore.ReloadAll() return gid, FPStore.ReloadAll()
if err != nil { //return gid, TopicList.RebuildPermTree()
return gid, err
}
err = TopicList.RebuildPermTree()
if err != nil {
return gid, err
}
return gid, nil
} }
func (mgs *MemoryGroupStore) GetAll() (results []*Group, err error) { func (mgs *MemoryGroupStore) GetAll() (results []*Group, err error) {

View File

@ -29,6 +29,7 @@ type Header struct {
Zone string Zone string
Path string Path string
MetaDesc string MetaDesc string
StartedAt time.Time
Writer http.ResponseWriter Writer http.ResponseWriter
ExtData ExtData ExtData ExtData
} }

View File

@ -6,6 +6,7 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
"time"
) )
// nolint // nolint
@ -115,6 +116,10 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (header
Writer: w, Writer: w,
} }
// TODO: We should probably initialise header.ExtData // 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") header.AddSheet(theme.Name + "/panel.css")
if len(theme.Resources) > 0 { 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 { if user.Loggedin && !user.Active {
header.AddNotice("account_inactive") 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 { if len(theme.Resources) > 0 {
rlist := theme.Resources rlist := theme.Resources

View File

@ -96,6 +96,7 @@ type devConfig struct {
TemplateDebug bool TemplateDebug bool
Profiling bool Profiling bool
TestDB bool TestDB bool
NoFsnotify bool // Super Experimental!
} }
// configHolder is purely for having a big struct to unmarshal data into // configHolder is purely for having a big struct to unmarshal data into

View File

@ -12,8 +12,8 @@ import (
"time" "time"
"github.com/Azareal/Gosora/common/alerts" "github.com/Azareal/Gosora/common/alerts"
"github.com/Azareal/Gosora/common/templates"
"github.com/Azareal/Gosora/common/phrases" "github.com/Azareal/Gosora/common/phrases"
"github.com/Azareal/Gosora/common/templates"
) )
var Ctemplates []string var Ctemplates []string
@ -162,15 +162,14 @@ func tmplInitHeaders(user User, user2 User, user3 User) (*Header, *Header, *Head
}, },
} }
var header2 = &Header{Site: Site} buildHeader := func(user User) *Header {
*header2 = *header var head = &Header{Site: Site}
header2.CurrentUser = user2 *head = *header
head.CurrentUser = user
return head
}
var header3 = &Header{Site: Site} return header, buildHeader(user2), buildHeader(user3)
*header3 = *header
header3.CurrentUser = user3
return header, header2, header3
} }
// ? - Add template hooks? // ? - Add template hooks?
@ -527,6 +526,10 @@ func InitTemplates() error {
return template.HTML(BuildWidget(dock.(string), headerInt.(*Header))) 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{} { fmap["lang"] = func(phraseNameInt interface{}) interface{} {
phraseName, ok := phraseNameInt.(string) phraseName, ok := phraseNameInt.(string)
if !ok { if !ok {

View File

@ -14,6 +14,7 @@ import (
// TODO: Turn this file into a library // TODO: Turn this file into a library
var textOverlapList = make(map[string]int) var textOverlapList = make(map[string]int)
// TODO: Stop hard-coding this here // TODO: Stop hard-coding this here
var langPkg = "github.com/Azareal/Gosora/common/phrases" var langPkg = "github.com/Azareal/Gosora/common/phrases"
@ -86,6 +87,7 @@ func NewCTemplateSet() *CTemplateSet {
"multiply": true, "multiply": true,
"divide": true, "divide": true,
"dock": true, "dock": true,
"elapsed": true,
"lang": true, "lang": true,
"level": true, "level": true,
"scope": 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) { 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") 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: ArgLoop:
for pos := 0; pos < len(node.Args); pos++ { for pos := 0; pos < len(node.Args); pos++ {
id := node.Args[pos] id := node.Args[pos]
@ -638,43 +658,21 @@ ArgLoop:
var rout string var rout string
pos, rout = c.compareJoin(varholder, holdreflect, templateName, pos, node, c.funcMap[id.String()].(string)) // TODO: Test this pos, rout = c.compareJoin(varholder, holdreflect, templateName, pos, node, c.funcMap[id.String()].(string)) // TODO: Test this
out += rout out += rout
case "le": // TODO: Can we condense these comparison cases down into one? case "le", "lt", "gt", "ge", "eq", "ne":
out += c.compareFunc(varholder, holdreflect, templateName, pos, node, "<=") out += c.compareFunc(varholder, holdreflect, templateName, pos, node, compOpMappings[id.String()])
break ArgLoop break ArgLoop
case "lt": case "add", "subtract", "divide", "multiply":
out += c.compareFunc(varholder, holdreflect, templateName, pos, node, "<") rout, rval := c.simpleMath(varholder, holdreflect, templateName, pos, node, mathOpMappings[id.String()])
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, "+")
out += rout out += rout
val = rval val = rval
break ArgLoop break ArgLoop
case "subtract": case "elapsed":
rout, rval := c.simpleMath(varholder, holdreflect, templateName, pos, node, "-") leftOperand := node.Args[pos+1].String()
out += rout leftParam, _ := c.compileIfVarsub(leftOperand, varholder, templateName, holdreflect)
val = rval // TODO: Refactor this
break ArgLoop // TODO: Validate that this is actually a time.Time
case "divide": litString("time.Since(" + leftParam + ").String()")
rout, rval := c.simpleMath(varholder, holdreflect, templateName, pos, node, "/") c.importMap["time"] = "time"
out += rout
val = rval
break ArgLoop
case "multiply":
rout, rval := c.simpleMath(varholder, holdreflect, templateName, pos, node, "*")
out += rout
val = rval
break ArgLoop break ArgLoop
case "dock": case "dock":
var leftParam, rightParam string var leftParam, rightParam string
@ -701,40 +699,33 @@ ArgLoop:
val = val3 val = val3
// TODO: Refactor this // TODO: Refactor this
out = "w.Write([]byte(common.BuildWidget(" + leftParam + "," + rightParam + ")))\n" litString("common.BuildWidget(" + leftParam + "," + rightParam + ")")
literal = true
break ArgLoop break ArgLoop
case "lang": case "lang":
var leftParam string
// TODO: Implement string literals properly // TODO: Implement string literals properly
leftOperand := node.Args[pos+1].String() leftOperand := node.Args[pos+1].String()
if len(leftOperand) == 0 { if len(leftOperand) == 0 {
panic("The left operand for the language string cannot be left blank") panic("The left operand for the language string cannot be left blank")
} }
if leftOperand[0] != '"' {
if leftOperand[0] == '"' {
// ! Slightly crude but it does the job
leftParam = strings.Replace(leftOperand, "\"", "", -1)
} else {
panic("Phrase names cannot be dynamic") panic("Phrase names cannot be dynamic")
} }
// ! Slightly crude but it does the job
leftParam := strings.Replace(leftOperand, "\"", "", -1)
c.langIndexToName = append(c.langIndexToName, leftParam) c.langIndexToName = append(c.langIndexToName, leftParam)
out = "w.Write(plist[" + strconv.Itoa(len(c.langIndexToName)-1) + "])\n" litString("plist[" + strconv.Itoa(len(c.langIndexToName)-1) + "]")
literal = true
break ArgLoop break ArgLoop
case "level": case "level":
var leftParam string
// TODO: Implement level literals // TODO: Implement level literals
leftOperand := node.Args[pos+1].String() leftOperand := node.Args[pos+1].String()
if len(leftOperand) == 0 { if len(leftOperand) == 0 {
panic("The leftoperand for function level cannot be left blank") 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 // TODO: Refactor this
out = "w.Write([]byte(phrases.GetLevelPhrase(" + leftParam + ")))\n" litString("phrases.GetLevelPhrase(" + leftParam + ")")
literal = true
c.importMap[langPkg] = langPkg c.importMap[langPkg] = langPkg
break ArgLoop break ArgLoop
case "scope": case "scope":

View File

@ -289,10 +289,25 @@ func ResetTemplateOverrides() {
log.Print("All of the template overrides have been reset") 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. // 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: Generate the type switch instead of writing it by hand
// TODO: Cut the number of types in half // TODO: Cut the number of types in half
func RunThemeTemplate(theme string, template string, pi interface{}, w io.Writer) error { 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) var getTmpl = GetThemeTemplate(theme, template)
switch tmplO := getTmpl.(type) { switch tmplO := getTmpl.(type) {
case *func(CustomPagePage, io.Writer) error: case *func(CustomPagePage, io.Writer) error:

View File

@ -3,7 +3,6 @@ package common
import ( import (
"strconv" "strconv"
"sync" "sync"
"sync/atomic"
"github.com/Azareal/Gosora/query_gen" "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) 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) 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) GetList(page int, orderby string) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error)
RebuildPermTree() error
} }
type DefaultTopicList struct { type DefaultTopicList struct {
@ -31,7 +28,7 @@ type DefaultTopicList struct {
oddLock sync.RWMutex oddLock sync.RWMutex
evenLock sync.RWMutex evenLock sync.RWMutex
permTree atomic.Value // [string(canSee)]canSee //permTree atomic.Value // [string(canSee)]canSee
//permTree map[string][]int // [string(canSee)]canSee //permTree map[string][]int // [string(canSee)]canSee
} }
@ -44,11 +41,7 @@ func NewDefaultTopicList() (*DefaultTopicList, error) {
evenGroups: make(map[int]*TopicListHolder), evenGroups: make(map[int]*TopicListHolder),
} }
err := tList.RebuildPermTree() err := tList.Tick()
if err != nil {
return nil, err
}
err = tList.Tick()
if err != nil { if err != nil {
return nil, err 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() allGroups, err := Groups.GetAll()
if err != nil { if err != nil {
return err return err
} }
var gidToCanSee = make(map[int]string)
var permTree = make(map[string][]int) // [string(canSee)]canSee
for _, group := range allGroups { 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 // ? - 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 { if group.UserCount == 0 && group.ID != GuestUser.Group {
@ -92,7 +79,23 @@ func (tList *DefaultTopicList) Tick() error {
for i, item := range group.CanSee { for i, item := range group.CanSee {
canSee[i] = byte(item) 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() tList.oddLock.Lock()
@ -106,28 +109,6 @@ func (tList *DefaultTopicList) Tick() error {
return nil 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) { func (tList *DefaultTopicList) GetListByGroup(group *Group, page int, orderby string) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) {
if page == 0 { if page == 0 {
page = 1 page = 1

View File

@ -10,7 +10,6 @@ import (
"sync" "sync"
"sync/atomic" "sync/atomic"
"errors" "errors"
"io"
"os" "os"
"net/http" "net/http"
@ -536,15 +535,6 @@ func init() {
counters.SetReverseOSMapEnum(reverseOSMapEnum) 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 { type WriterIntercept struct {
http.ResponseWriter http.ResponseWriter
} }
@ -889,7 +879,7 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
gz.Close() gz.Close()
} }
}() }()
w = gzipResponseWriter{Writer: gz, ResponseWriter: w} w = common.GzipResponseWriter{Writer: gz, ResponseWriter: w}
} }
ferr := r.routeSwitch(w, req, user, prefix, extraData) 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 return err
} }
gzw, ok := w.(gzipResponseWriter) gzw, ok := w.(common.GzipResponseWriter)
if ok { if ok {
w = gzw.ResponseWriter w = gzw.ResponseWriter
w.Header().Del("Content-Type") w.Header().Del("Content-Type")
@ -1406,7 +1396,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
gzw, ok := w.(gzipResponseWriter) gzw, ok := w.(common.GzipResponseWriter)
if ok { if ok {
w = gzw.ResponseWriter w = gzw.ResponseWriter
w.Header().Del("Content-Type") w.Header().Del("Content-Type")
@ -1982,7 +1972,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
if extraData == "" { if extraData == "" {
return common.NotFound(w,req,nil) return common.NotFound(w,req,nil)
} }
gzw, ok := w.(gzipResponseWriter) gzw, ok := w.(common.GzipResponseWriter)
if ok { if ok {
w = gzw.ResponseWriter w = gzw.ResponseWriter
w.Header().Del("Content-Type") w.Header().Del("Content-Type")

View File

@ -108,6 +108,8 @@ func init() {
} }
} }
const benchTid = "1"
// TODO: Swap out LocalError for a panic for this? // TODO: Swap out LocalError for a panic for this?
func BenchmarkTopicAdminRouteParallel(b *testing.B) { func BenchmarkTopicAdminRouteParallel(b *testing.B) {
binit(b) binit(b)
@ -128,7 +130,7 @@ func BenchmarkTopicAdminRouteParallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
w := httptest.NewRecorder() 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(&adminUIDCookie)
reqAdmin.AddCookie(&adminSessionCookie) reqAdmin.AddCookie(&adminSessionCookie)
@ -176,7 +178,7 @@ func BenchmarkTopicAdminRouteParallelWithRouter(b *testing.B) {
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
w := httptest.NewRecorder() 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(&uidCookie)
reqAdmin.AddCookie(&sessionCookie) 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") 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) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
w := httptest.NewRecorder() 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 user := common.GuestUser
head, err := common.UserCheck(w, req, &user) head, err := common.UserCheck(w, req, &user)
@ -242,7 +244,7 @@ func BenchmarkTopicGuestRouteParallelDebugMode(b *testing.B) {
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
w := httptest.NewRecorder() 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 user := common.GuestUser
head, err := common.UserCheck(w, req, &user) head, err := common.UserCheck(w, req, &user)
@ -260,65 +262,6 @@ func BenchmarkTopicGuestRouteParallelDebugMode(b *testing.B) {
cfg.Restore() 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) { func obRoute(b *testing.B, path string) {
binit(b) binit(b)
cfg := NewStashConfig() cfg := NewStashConfig()
@ -340,6 +283,14 @@ func BenchmarkForumGuestRouteParallelWithRouter(b *testing.B) {
obRoute(b, "/forum/general.2") 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) { func binit(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
err := gloinit() err := gloinit()

142
main.go
View File

@ -16,6 +16,7 @@ import (
"net/http" "net/http"
"os" "os"
"os/signal" "os/signal"
"runtime"
"runtime/pprof" "runtime/pprof"
"strings" "strings"
"syscall" "syscall"
@ -24,8 +25,8 @@ import (
"github.com/Azareal/Gosora/common" "github.com/Azareal/Gosora/common"
"github.com/Azareal/Gosora/common/counters" "github.com/Azareal/Gosora/common/counters"
"github.com/Azareal/Gosora/common/phrases" "github.com/Azareal/Gosora/common/phrases"
"github.com/Azareal/Gosora/routes"
"github.com/Azareal/Gosora/query_gen" "github.com/Azareal/Gosora/query_gen"
"github.com/Azareal/Gosora/routes"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -289,72 +290,74 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
log.Print("Initialising the file watcher") if !common.Dev.NoFsnotify {
watcher, err := fsnotify.NewWatcher() log.Print("Initialising the file watcher")
if err != nil { watcher, err := fsnotify.NewWatcher()
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 { if err != nil {
log.Fatal(err) 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") log.Print("Initialising the task system")
@ -437,6 +440,17 @@ func main() {
args := <-common.StopServerChan args := <-common.StopServerChan
if false { if false {
pprof.StopCPUProfile() 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? // Why did the server stop?
log.Fatal(args...) log.Fatal(args...)

View File

@ -239,7 +239,6 @@ import (
"sync" "sync"
"sync/atomic" "sync/atomic"
"errors" "errors"
"io"
"os" "os"
"net/http" "net/http"
@ -318,15 +317,6 @@ func init() {
counters.SetReverseOSMapEnum(reverseOSMapEnum) 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 { type WriterIntercept struct {
http.ResponseWriter http.ResponseWriter
} }
@ -671,7 +661,7 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
gz.Close() gz.Close()
} }
}() }()
w = gzipResponseWriter{Writer: gz, ResponseWriter: w} w = common.GzipResponseWriter{Writer: gz, ResponseWriter: w}
} }
ferr := r.routeSwitch(w, req, user, prefix, extraData) 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 == "" { if extraData == "" {
return common.NotFound(w,req,nil) return common.NotFound(w,req,nil)
} }
gzw, ok := w.(gzipResponseWriter) gzw, ok := w.(common.GzipResponseWriter)
if ok { if ok {
w = gzw.ResponseWriter w = gzw.ResponseWriter
w.Header().Del("Content-Type") w.Header().Del("Content-Type")

View File

@ -60,7 +60,7 @@ func (route *RouteImpl) hasBeforeItem(item string) bool {
} }
func (route *RouteImpl) NoGzip() *RouteImpl { func (route *RouteImpl) NoGzip() *RouteImpl {
return route.LitBeforeMultiline(`gzw, ok := w.(gzipResponseWriter) return route.LitBeforeMultiline(`gzw, ok := w.(common.GzipResponseWriter)
if ok { if ok {
w = gzw.ResponseWriter w = gzw.ResponseWriter
w.Header().Del("Content-Type") w.Header().Del("Content-Type")

View File

@ -9,6 +9,7 @@
<div id="poweredBy"> <div id="poweredBy">
<a id="poweredByName" href="https://github.com/Azareal/Gosora">{{lang "footer_powered_by"}}</a><span id="poweredByDash"> - </span><span id="poweredByMaker">{{lang "footer_made_with_love"}}</span> <a id="poweredByName" href="https://github.com/Azareal/Gosora">{{lang "footer_powered_by"}}</a><span id="poweredByDash"> - </span><span id="poweredByMaker">{{lang "footer_made_with_love"}}</span>
</div> </div>
{{if .CurrentUser.IsAdmin}}<div class="elapsed">{{elapsed .Header.StartedAt}}</div>{{end}}
<form action="/theme/" method="post"> <form action="/theme/" method="post">
<div id="themeSelector" style="float: right;"> <div id="themeSelector" style="float: right;">
<select id="themeSelectorSelect" name="themeSelector" aria-label="{{lang "footer_theme_selector_aria"}}"> <select id="themeSelectorSelect" name="themeSelector" aria-label="{{lang "footer_theme_selector_aria"}}">

View File

@ -1413,6 +1413,9 @@ textarea {
background-color: var(--element-background-color); background-color: var(--element-background-color);
display: flex; display: flex;
} }
.elapsed {
display: none;
}
#poweredByHolder { #poweredByHolder {
border-bottom: 2px solid var(--element-border-color); border-bottom: 2px solid var(--element-border-color);
} }

View File

@ -588,6 +588,9 @@ button, .formbutton, .panel_right_button:not(.has_inner_button) {
.forum_list .forum_right span { .forum_list .forum_right span {
line-height: 19px; line-height: 19px;
} }
.forum_list .forum_right span a {
white-space: nowrap;
}
.extra_little_row_avatar { .extra_little_row_avatar {
border-radius: 24px; border-radius: 24px;
height: 36px; height: 36px;
@ -837,10 +840,21 @@ input[type=checkbox]:checked + label .sel {
margin-right: 8px; margin-right: 8px;
} }
.footer .widget { .footer .widget, .elapsed {
padding: 12px; padding: 12px;
border-bottom: 1px solid #555555; border-bottom: 1px solid #555555;
} }
.elapsed {
padding: 6px;
background: rgb(82,82,82);
border-radius: 3px;
font-size: 13.5px;
color: rgb(200,200,200);
margin-top: 1px;
margin-bottom: 4px;
padding-bottom: 2px;
padding-top: 3px;
}
#poweredByHolder { #poweredByHolder {
display: flex; display: flex;
padding-top: 12px; padding-top: 12px;

View File

@ -573,6 +573,9 @@ input, select, textarea {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
} }
.elapsed {
display: none;
}
#poweredByHolder { #poweredByHolder {
background-color: var(--main-block-color); background-color: var(--main-block-color);
padding: 10px; padding: 10px;

View File

@ -856,6 +856,9 @@ input[type=checkbox]:checked + label.poll_option_label .sel {
top: -2px; top: -2px;
} }
.elapsed {
display: none;
}
#poweredByHolder { #poweredByHolder {
border: 1px solid hsl(0, 0%, 80%); border: 1px solid hsl(0, 0%, 80%);
margin-top: 12px; margin-top: 12px;