initial perf anaytics

add tasks to debug page
ignore .git on debug page for speed

add perfchunks table

Renamed phrases (changed statistics to stats):
panel_menu_stats
panel_menu_stats_posts
panel_menu_stats_topics
panel_menu_stats_forums
panel_menu_stats_routes
panel_menu_stats_agents
panel_menu_stats_systems
panel_menu_stats_languages
panel_menu_stats_referrers
panel_menu_stats_memory
panel_menu_stats_active_memory
panel_menu_stats_perf
panel_stats_views_head_suffix
panel_stats_user_agents_head
panel_stats_forums_head
panel_stats_languages_head
panel_stats_post_counts_head
panel_stats_referrers_head
panel_stats_routes_head
panel_stats_operating_systems_head
panel_stats_topic_counts_head
panel_stats_requests_head
panel_stats_memory_head
panel_stats_active_memory_head
panel_stats_spam_hide
panel_stats_spam_show
panel_stats_memory_type_total
panel_stats_memory_type_stack
panel_stats_memory_type_heap
panel_stats_time_range_one_year
panel_stats_time_range_three_months
panel_stats_time_range_one_month
panel_stats_time_range_one_week
panel_stats_time_range_two_days
panel_stats_time_range_one_day
panel_stats_time_range_twelve_hours
panel_stats_time_range_six_hours
panel_stats_post_counts_chart_aria
panel_stats_topic_counts_chart_aria
panel_stats_requests_chart_aria
panel_stats_memory_chart_aria
panel_stats_details_head
panel_stats_post_counts_table_aria
panel_stats_topic_counts_table_aria
panel_stats_route_views_table_aria
panel_stats_requests_table_aria
panel_stats_memory_table_aria
panel_stats_views_suffix
panel_stats_posts_suffix
panel_stats_topics_suffix
panel_stats_user_agents_no_user_agents
panel_stats_forums_no_forums
panel_stats_languages_no_languages
panel_stats_post_counts_no_post_counts
panel_stats_referrers_no_referrers
panel_stats_routes_no_routes
panel_stats_operating_systems_no_operating_systems
panel_stats_memory_no_memory

Added phrases:
panel_debug_tasks
panel_debug_tasks_half_second
panel_debug_tasks_second
panel_debug_tasks_fifteen_minute
panel_debug_tasks_hour
panel_debug_tasks_shutdown
panel_stats_perf_head
panel_stats_perf_low
panel_stats_perf_high
panel_stats_perf_avg

You will need to run the updater / patcher for this commit.
This commit is contained in:
Azareal 2020-02-23 19:08:47 +10:00
parent 24cef43439
commit 459d745cb1
51 changed files with 985 additions and 521 deletions

View File

@ -733,6 +733,15 @@ func createTables(adapter qgen.Adapter) (err error) {
}, nil,
)
createTable("perfchunks", "", "",
[]tC{
tC{"low", "int", 0, false, false, "0"},
tC{"high", "int", 0, false, false, "0"},
tC{"avg", "int", 0, false, false, "0"},
tC{"createdAt", "datetime", 0, false, false, ""},
}, nil,
)
createTable("sync", "", "",
[]tC{
tC{"last_update", "datetime", 0, false, false, ""},

View File

@ -35,10 +35,12 @@ type SQLModLogStore struct {
func NewModLogStore(acc *qgen.Accumulator) (*SQLModLogStore, error) {
ml := "moderation_logs"
// TODO: Shorten name of ipaddress column to ip
cols := "action, elementID, elementType, ipaddress, actorID, doneAt, extra"
return &SQLModLogStore{
create: acc.Insert(ml).Columns("action, elementID, elementType, ipaddress, actorID, doneAt, extra").Fields("?,?,?,?,?,UTC_TIMESTAMP(),?").Prepare(),
create: acc.Insert(ml).Columns(cols).Fields("?,?,?,?,?,UTC_TIMESTAMP(),?").Prepare(),
count: acc.Count(ml).Prepare(),
getOffset: acc.Select(ml).Columns("action, elementID, elementType, ipaddress, actorID, doneAt, extra").Orderby("doneAt DESC").Limit("?,?").Prepare(),
getOffset: acc.Select(ml).Columns(cols).Orderby("doneAt DESC").Limit("?,?").Prepare(),
}, acc.FirstError()
}
@ -91,10 +93,11 @@ type SQLAdminLogStore struct {
func NewAdminLogStore(acc *qgen.Accumulator) (*SQLAdminLogStore, error) {
al := "administration_logs"
cols := "action, elementID, elementType, ipaddress, actorID, doneAt, extra"
return &SQLAdminLogStore{
create: acc.Insert(al).Columns("action, elementID, elementType, ipaddress, actorID, doneAt, extra").Fields("?,?,?,?,?,UTC_TIMESTAMP(),?").Prepare(),
create: acc.Insert(al).Columns(cols).Fields("?,?,?,?,?,UTC_TIMESTAMP(),?").Prepare(),
count: acc.Count(al).Prepare(),
getOffset: acc.Select(al).Columns("action, elementID, elementType, ipaddress, actorID, doneAt, extra").Orderby("doneAt DESC").Limit("?,?").Prepare(),
getOffset: acc.Select(al).Columns(cols).Orderby("doneAt DESC").Limit("?,?").Prepare(),
}, acc.FirstError()
}

View File

@ -40,3 +40,13 @@ type RWMutexCounterBucket struct {
counter int
sync.RWMutex
}
type MutexCounterBucket struct {
counter int
sync.Mutex
}
type MutexCounter64Bucket struct {
counter int64
sync.Mutex
}

View File

@ -0,0 +1,113 @@
package counters
import (
"database/sql"
"sync/atomic"
"time"
"math"
c "github.com/Azareal/Gosora/common"
qgen "github.com/Azareal/Gosora/query_gen"
"github.com/pkg/errors"
)
var PerfCounter *DefaultPerfCounter
type PerfCounterBucket struct {
low *MutexCounter64Bucket
high *MutexCounter64Bucket
avg *MutexCounter64Bucket
}
// TODO: Track perf on a per route basis
type DefaultPerfCounter struct {
buckets []PerfCounterBucket
insert *sql.Stmt
}
func NewDefaultPerfCounter(acc *qgen.Accumulator) (*DefaultPerfCounter, error) {
co := &DefaultPerfCounter{
buckets: []PerfCounterBucket{
PerfCounterBucket{
low: &MutexCounter64Bucket{counter: 0},
high: &MutexCounter64Bucket{counter: 0},
avg: &MutexCounter64Bucket{counter: 0},
},
},
insert: acc.Insert("perfchunks").Columns("low,high,avg,createdAt").Fields("?,?,?,UTC_TIMESTAMP()").Prepare(),
}
c.AddScheduledFifteenMinuteTask(co.Tick)
//c.AddScheduledSecondTask(co.Tick)
c.AddShutdownTask(co.Tick)
return co, acc.FirstError()
}
func (co *DefaultPerfCounter) Tick() error {
getCounter := func(b *MutexCounter64Bucket) int64 {
return atomic.SwapInt64(&b.counter, 0)
}
for _, b := range co.buckets {
low := atomic.SwapInt64(&b.low.counter, math.MaxInt64)
if low == math.MaxInt64 {
low = 0
}
high := getCounter(b.high)
avg := getCounter(b.avg)
err := co.insertChunk(low, high, avg) // TODO: Bulk insert for speed?
if err != nil {
return errors.Wrap(errors.WithStack(err), "perf counter")
}
}
return nil
}
func (co *DefaultPerfCounter) insertChunk(low, high, avg int64) error {
if low == 0 && high == 0 && avg == 0 {
return nil
}
c.DebugLogf("Inserting a pchunk with low %d, high %d, avg %d", low, high, avg)
_, err := co.insert.Exec(low, high, avg)
return err
}
func (co *DefaultPerfCounter) Push(dur time.Duration) {
id := 0
b := co.buckets[id]
//c.DebugDetail("co.buckets[", id, "]: ", b)
micro := dur.Microseconds()
low := b.low
if micro < low.counter {
low.Lock()
if micro < low.counter {
atomic.StoreInt64(&low.counter,micro)
}
low.Unlock()
}
high := b.high
if micro > high.counter {
high.Lock()
if micro > high.counter {
atomic.StoreInt64(&high.counter,micro)
}
high.Unlock()
}
avg := b.avg
// TODO: Sync semantics are slightly loose but it should be close enough for our purposes here
if micro != avg.counter {
t := false
avg.Lock()
if avg.counter == 0 {
t = atomic.CompareAndSwapInt64(&avg.counter, 0, micro)
}
if !t && micro != avg.counter {
atomic.StoreInt64(&avg.counter,(micro+avg.counter) / 2)
}
avg.Unlock()
}
}

View File

@ -5,7 +5,7 @@ import (
"sync/atomic"
c "github.com/Azareal/Gosora/common"
"github.com/Azareal/Gosora/query_gen"
qgen "github.com/Azareal/Gosora/query_gen"
"github.com/pkg/errors"
)
@ -22,7 +22,7 @@ func NewPostCounter() (*DefaultPostCounter, error) {
acc := qgen.NewAcc()
co := &DefaultPostCounter{
currentBucket: 0,
insert: acc.Insert("postchunks").Columns("count, createdAt").Fields("?,UTC_TIMESTAMP()").Prepare(),
insert: acc.Insert("postchunks").Columns("count,createdAt").Fields("?,UTC_TIMESTAMP()").Prepare(),
}
c.AddScheduledFifteenMinuteTask(co.Tick)
//c.AddScheduledSecondTask(co.Tick)
@ -44,7 +44,7 @@ func (co *DefaultPostCounter) Tick() (err error) {
atomic.AddInt64(&co.buckets[oldBucket], -previousViewChunk)
err = co.insertChunk(previousViewChunk)
if err != nil {
return errors.Wrap(errors.WithStack(err),"post counter")
return errors.Wrap(errors.WithStack(err), "post counter")
}
return nil
}

View File

@ -6,7 +6,7 @@ import (
"sync/atomic"
c "github.com/Azareal/Gosora/common"
"github.com/Azareal/Gosora/query_gen"
qgen "github.com/Azareal/Gosora/query_gen"
"github.com/pkg/errors"
)
@ -35,7 +35,7 @@ func NewDefaultReferrerTracker() (*DefaultReferrerTracker, error) {
refTracker := &DefaultReferrerTracker{
odd: make(map[string]*ReferrerItem),
even: make(map[string]*ReferrerItem),
insert: acc.Insert("viewchunks_referrers").Columns("count, createdAt, domain").Fields("?,UTC_TIMESTAMP(),?").Prepare(), // TODO: Do something more efficient than doing a query for each referrer
insert: acc.Insert("viewchunks_referrers").Columns("count,createdAt,domain").Fields("?,UTC_TIMESTAMP(),?").Prepare(), // TODO: Do something more efficient than doing a query for each referrer
}
c.AddScheduledFifteenMinuteTask(refTracker.Tick)
//c.AddScheduledSecondTask(refTracker.Tick)
@ -51,7 +51,7 @@ func (ref *DefaultReferrerTracker) Tick() (err error) {
if count != 0 {
err := ref.insertChunk(referrer, count) // TODO: Bulk insert for speed?
if err != nil {
return errors.Wrap(errors.WithStack(err),"ref counter")
return errors.Wrap(errors.WithStack(err), "ref counter")
}
}
delete(referrersToDelete, referrer)
@ -69,16 +69,16 @@ func (ref *DefaultReferrerTracker) Tick() (err error) {
count := atomic.SwapInt64(&counter.Count, 0)
err := ref.insertChunk(referrer, count) // TODO: Bulk insert for speed?
if err != nil {
return errors.Wrap(errors.WithStack(err),"ref counter")
return errors.Wrap(errors.WithStack(err), "ref counter")
}
}
return nil
}
err = refLoop(&ref.oddLock,ref.odd)
err = refLoop(&ref.oddLock, ref.odd)
if err != nil {
return err
}
return refLoop(&ref.evenLock,ref.even)
return refLoop(&ref.evenLock, ref.even)
}
func (ref *DefaultReferrerTracker) insertChunk(referrer string, count int64) error {

View File

@ -23,7 +23,7 @@ type DefaultViewCounter struct {
func NewGlobalViewCounter(acc *qgen.Accumulator) (*DefaultViewCounter, error) {
co := &DefaultViewCounter{
currentBucket: 0,
insert: acc.Insert("viewchunks").Columns("count, createdAt, route").Fields("?,UTC_TIMESTAMP(),''").Prepare(),
insert: acc.Insert("viewchunks").Columns("count,createdAt,route").Fields("?,UTC_TIMESTAMP(),''").Prepare(),
}
c.AddScheduledFifteenMinuteTask(co.Tick) // This is run once every fifteen minutes to match the frequency of the RouteViewCounter
//c.AddScheduledSecondTask(co.Tick)
@ -31,6 +31,7 @@ func NewGlobalViewCounter(acc *qgen.Accumulator) (*DefaultViewCounter, error) {
return co, acc.FirstError()
}
// TODO: Simplify the atomics used here
func (co *DefaultViewCounter) Tick() (err error) {
oldBucket := co.currentBucket
var nextBucket int64 // 0

View File

@ -354,6 +354,14 @@ type PanelAnalyticsActiveMemory struct {
TimeType string
MemType int
}
type PanelAnalyticsPerf struct {
Graph PanelTimeGraph
ViewItems []PanelAnalyticsItemUnit
TimeRange string
Unit string
TimeType string
PerfType int
}
type PanelStats struct {
Users int
@ -654,6 +662,14 @@ type PanelRegLogsPage struct {
Paginator
}
type DebugPageTasks struct {
HalfSecond int
Second int
FifteenMinute int
Hour int
Shutdown int
}
type DebugPageCache struct {
Topics int
Users int
@ -711,8 +727,9 @@ type PanelDebugPage struct {
Goroutines int
CPUs int
MemStats runtime.MemStats
Tasks DebugPageTasks
MemStats runtime.MemStats
Cache DebugPageCache
Database DebugPageDatabase
Disk DebugPageDisk

View File

@ -113,25 +113,25 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (header
}
// 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 {
//if user.IsAdmin {
header.StartedAt = time.Now()
}
//}
header.AddSheet(theme.Name + "/main.css")
header.AddSheet(theme.Name + "/panel.css")
if len(theme.Resources) > 0 {
rlist := theme.Resources
for _, resource := range rlist {
if resource.Location == "global" || resource.Location == "panel" {
extarr := strings.Split(resource.Name, ".")
for _, res := range rlist {
if res.Location == "global" || res.Location == "panel" {
extarr := strings.Split(res.Name, ".")
ext := extarr[len(extarr)-1]
if ext == "css" {
header.AddSheet(resource.Name)
header.AddSheet(res.Name)
} else if ext == "js" {
if resource.Async {
header.AddScriptAsync(resource.Name)
if res.Async {
header.AddScriptAsync(res.Name)
} else {
header.AddScript(resource.Name)
header.AddScript(res.Name)
}
}
}
@ -224,9 +224,9 @@ func userCheck(w http.ResponseWriter, r *http.Request, user *User) (header *Head
// 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 {
//if user.IsAdmin {
header.StartedAt = time.Now()
}
//}
//PrepResources(user,header,theme)
return header, nil
@ -237,20 +237,20 @@ func PrepResources(user *User, h *Header, theme *Theme) {
if len(theme.Resources) > 0 {
rlist := theme.Resources
for _, resource := range rlist {
if resource.Loggedin && !user.Loggedin {
for _, res := range rlist {
if res.Loggedin && !user.Loggedin {
continue
}
if resource.Location == "global" || resource.Location == "frontend" {
extarr := strings.Split(resource.Name, ".")
if res.Location == "global" || res.Location == "frontend" {
extarr := strings.Split(res.Name, ".")
ext := extarr[len(extarr)-1]
if ext == "css" {
h.AddSheet(resource.Name)
h.AddSheet(res.Name)
} else if ext == "js" {
if resource.Async {
h.AddScriptAsync(resource.Name)
if res.Async {
h.AddScriptAsync(res.Name)
} else {
h.AddScript(resource.Name)
h.AddScript(res.Name)
}
}
}

View File

@ -64,6 +64,27 @@ func AddShutdownTask(task func() error) {
ShutdownTasks = append(ShutdownTasks, task)
}
// ScheduledHalfSecondTaskCount is not concurrency safe
func ScheduledHalfSecondTaskCount() int {
return len(ScheduledHalfSecondTasks)
}
// ScheduledSecondTaskCount is not concurrency safe
func ScheduledSecondTaskCount() int {
return len(ScheduledSecondTasks)
}
// ScheduledFifteenMinuteTaskCount is not concurrency safe
func ScheduledFifteenMinuteTaskCount() int {
return len(ScheduledFifteenMinuteTasks)
}
// ScheduledHourTaskCount is not concurrency safe
func ScheduledHourTaskCount() int {
return len(ScheduledHourTasks)
}
// ShutdownTaskCount is not concurrency safe
func ShutdownTaskCount() int {
return len(ShutdownTasks)
}
// TODO: Use AddScheduledSecondTask
func HandleExpiredScheduledGroups() error {
rows, err := taskStmts.getExpiredScheduledGroups.Query()

View File

@ -372,10 +372,11 @@ func compileTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName string
dbVersion := qgen.Builder.DbVersion()
var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)
debugTasks := DebugPageTasks{0,0,0,0,0}
debugCache := DebugPageCache{1, 1, 1, 2, 2, 2, true}
debugDatabase := DebugPageDatabase{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
debugDisk := DebugPageDisk{1, 1, 1, 1, 1, 1}
dpage := PanelDebugPage{basePage, goVersion, dbVersion, "0s", 1, qgen.Builder.GetAdapter().GetName(), 1, 1, memStats, debugCache, debugDatabase, debugDisk}
dpage := PanelDebugPage{basePage, goVersion, dbVersion, "0s", 1, qgen.Builder.GetAdapter().GetName(), 1, 1, debugTasks, memStats, debugCache, debugDatabase, debugDisk}
t.AddStd("panel_debug", "c.PanelDebugPage", dpage)
//t.AddStd("panel_analytics", "c.PanelAnalytics", Panel{basePage, "panel_dashboard_right","panel_dashboard", inter})

File diff suppressed because it is too large Load Diff

View File

@ -795,17 +795,18 @@
"panel_menu_menus":"Menus",
"panel_menu_widgets":"Widgets",
"panel_menu_events":"Events",
"panel_menu_statistics":"Statistics",
"panel_menu_statistics_posts":"Posts",
"panel_menu_statistics_topics":"Topics",
"panel_menu_statistics_forums":"Forums",
"panel_menu_statistics_routes":"Routes",
"panel_menu_statistics_agents":"Agents",
"panel_menu_statistics_systems":"Systems",
"panel_menu_statistics_languages":"Languages",
"panel_menu_statistics_referrers":"Referrers",
"panel_menu_statistics_memory":"Memory",
"panel_menu_statistics_active_memory":"Active Memory",
"panel_menu_stats":"Statistics",
"panel_menu_stats_posts":"Posts",
"panel_menu_stats_topics":"Topics",
"panel_menu_stats_forums":"Forums",
"panel_menu_stats_routes":"Routes",
"panel_menu_stats_agents":"Agents",
"panel_menu_stats_systems":"Systems",
"panel_menu_stats_languages":"Languages",
"panel_menu_stats_referrers":"Referrers",
"panel_menu_stats_memory":"Memory",
"panel_menu_stats_active_memory":"Active Memory",
"panel_menu_stats_perf":"Performance",
"panel_menu_reports":"Reports",
"panel_menu_logs":"Logs",
"panel_menu_logs_registrations":"Registrations",
@ -975,56 +976,60 @@
"panel_pages_title":"Title",
"panel_pages_edit_update_button":"Update Page",
"panel_statistics_views_head_suffix":" Views",
"panel_statistics_user_agents_head":"User Agents",
"panel_statistics_forums_head":"Forums",
"panel_statistics_languages_head":"Languages",
"panel_statistics_post_counts_head":"Post Counts",
"panel_statistics_referrers_head":"Referrers",
"panel_statistics_routes_head":"Routes",
"panel_statistics_operating_systems_head":"Operating Systems",
"panel_statistics_topic_counts_head":"Topic Counts",
"panel_statistics_requests_head":"Requests",
"panel_statistics_memory_head":"Memory Usage",
"panel_statistics_active_memory_head":"Active Memory",
"panel_stats_views_head_suffix":" Views",
"panel_stats_user_agents_head":"User Agents",
"panel_stats_forums_head":"Forums",
"panel_stats_languages_head":"Languages",
"panel_stats_post_counts_head":"Post Counts",
"panel_stats_referrers_head":"Referrers",
"panel_stats_routes_head":"Routes",
"panel_stats_operating_systems_head":"Operating Systems",
"panel_stats_topic_counts_head":"Topic Counts",
"panel_stats_requests_head":"Requests",
"panel_stats_memory_head":"Memory Usage",
"panel_stats_active_memory_head":"Active Memory",
"panel_stats_perf_head":"Performance",
"panel_statistics_spam_hide":"Hide Spam",
"panel_statistics_spam_show":"Show Spam",
"panel_statistics_memory_type_total":"Total",
"panel_statistics_memory_type_stack":"Stack",
"panel_statistics_memory_type_heap":"Heap",
"panel_stats_spam_hide":"Hide Spam",
"panel_stats_spam_show":"Show Spam",
"panel_stats_memory_type_total":"Total",
"panel_stats_memory_type_stack":"Stack",
"panel_stats_memory_type_heap":"Heap",
"panel_stats_perf_low":"Low",
"panel_stats_perf_high":"High",
"panel_stats_perf_avg":"Average",
"panel_statistics_time_range_one_year":"1 year",
"panel_statistics_time_range_three_months":"3 months",
"panel_statistics_time_range_one_month":"1 month",
"panel_statistics_time_range_one_week":"1 week",
"panel_statistics_time_range_two_days":"2 days",
"panel_statistics_time_range_one_day":"1 day",
"panel_statistics_time_range_twelve_hours":"12 hours",
"panel_statistics_time_range_six_hours":"6 hours",
"panel_stats_time_range_one_year":"1 year",
"panel_stats_time_range_three_months":"3 months",
"panel_stats_time_range_one_month":"1 month",
"panel_stats_time_range_one_week":"1 week",
"panel_stats_time_range_two_days":"2 days",
"panel_stats_time_range_one_day":"1 day",
"panel_stats_time_range_twelve_hours":"12 hours",
"panel_stats_time_range_six_hours":"6 hours",
"panel_statistics_post_counts_chart_aria":"Post Chart",
"panel_statistics_topic_counts_chart_aria":"Topic Chart",
"panel_statistics_requests_chart_aria":"Requests Chart",
"panel_statistics_memory_chart_aria":"Memory Use Chart",
"panel_statistics_details_head":"Details",
"panel_statistics_post_counts_table_aria":"Post Table, this has the same information as the post chart",
"panel_statistics_topic_counts_table_aria":"Topic Table, this has the same information as the topic chart",
"panel_statistics_route_views_table_aria":"View Table, this has the same information as the view chart",
"panel_statistics_requests_table_aria":"View Table, this has the same information as the view chart",
"panel_statistics_memory_table_aria":"Memory Use Table, this has the same information as the memory use chart",
"panel_statistics_views_suffix":" views",
"panel_statistics_posts_suffix":" posts",
"panel_statistics_topics_suffix":" topics",
"panel_stats_post_counts_chart_aria":"Post Chart",
"panel_stats_topic_counts_chart_aria":"Topic Chart",
"panel_stats_requests_chart_aria":"Requests Chart",
"panel_stats_memory_chart_aria":"Memory Use Chart",
"panel_stats_details_head":"Details",
"panel_stats_post_counts_table_aria":"Post Table, this has the same information as the post chart",
"panel_stats_topic_counts_table_aria":"Topic Table, this has the same information as the topic chart",
"panel_stats_route_views_table_aria":"View Table, this has the same information as the view chart",
"panel_stats_requests_table_aria":"View Table, this has the same information as the view chart",
"panel_stats_memory_table_aria":"Memory Use Table, this has the same information as the memory use chart",
"panel_stats_views_suffix":" views",
"panel_stats_posts_suffix":" posts",
"panel_stats_topics_suffix":" topics",
"panel_statistics_user_agents_no_user_agents":"No user agents could be found in the selected time range",
"panel_statistics_forums_no_forums":"No forum view counts could be found in the selected time range",
"panel_statistics_languages_no_languages":"No language could be found in the selected time range",
"panel_statistics_post_counts_no_post_counts":"No posts could be found in the selected time range",
"panel_statistics_referrers_no_referrers":"No referrers could be found in the selected time range",
"panel_statistics_routes_no_routes":"No route view counts could be found in the selected time range",
"panel_statistics_operating_systems_no_operating_systems":"No operating systems could be found in the selected time range",
"panel_statistics_memory_no_memory":"No memory chunks could be found in the selected time range",
"panel_stats_user_agents_no_user_agents":"No user agents could be found in the selected time range",
"panel_stats_forums_no_forums":"No forum view counts could be found in the selected time range",
"panel_stats_languages_no_languages":"No language could be found in the selected time range",
"panel_stats_post_counts_no_post_counts":"No posts could be found in the selected time range",
"panel_stats_referrers_no_referrers":"No referrers could be found in the selected time range",
"panel_stats_routes_no_routes":"No route view counts could be found in the selected time range",
"panel_stats_operating_systems_no_operating_systems":"No operating systems could be found in the selected time range",
"panel_stats_memory_no_memory":"No memory chunks could be found in the selected time range",
"panel_logs_menu_head":"Logs",
"panel_logs_reg_head":"Registrations",
@ -1173,6 +1178,13 @@
"panel_debug_goroutine_count_label":"Goroutines",
"panel_debug_cpu_count_label":"CPUs",
"panel_debug_tasks":"Tasks",
"panel_debug_tasks_half_second":"Half Second",
"panel_debug_tasks_second":"Second",
"panel_debug_tasks_fifteen_minute":"Fifteen Minute",
"panel_debug_tasks_hour":"Hourly",
"panel_debug_tasks_shutdown":"Shutdown",
"panel_debug_memory_stats":"Memory Statistics",
"panel_debug_memory_stats_sys":"Sys",
"panel_debug_memory_stats_heapsys":"HeapSys",

View File

@ -49,6 +49,7 @@ func init() {
addPatch(29, patch29)
addPatch(30, patch30)
addPatch(31, patch31)
addPatch(32, patch32)
}
func patch0(scanner *bufio.Scanner) (err error) {
@ -892,3 +893,15 @@ func patch31(scanner *bufio.Scanner) error {
}
return nil
}
func patch32(scanner *bufio.Scanner) error {
return execStmt(qgen.Builder.CreateTable("perfchunks", "", "",
[]tC{
tC{"low", "int", 0, false, false, "0"},
tC{"high", "int", 0, false, false, "0"},
tC{"avg", "int", 0, false, false, "0"},
tC{"createdAt", "datetime", 0, false, false, ""},
}, nil,
))
}

View File

@ -38,6 +38,46 @@ function memStuff(window, document, Chartist) {
};
}
function perfStuff(window, document, Chartist) {
'use strict';
Chartist.plugins = Chartist.plugins || {};
Chartist.plugins.perfUnits = function(options) {
options = Chartist.extend({}, {}, options);
return function perfUnits(chart) {
if(!chart instanceof Chartist.Line) return;
chart.on('created', function() {
console.log("running created")
const vbits = document.getElementsByClassName("ct-vertical");
if(vbits==null) return;
let tbits = [];
for(let i = 0; i < vbits.length; i++) {
tbits[i] = vbits[i].innerHTML;
}
console.log("tbits:",tbits);
const calc = (places) => {
if(places==3) return;
const matcher = vbits[0].innerHTML;
let allMatch = true;
for(let i = 0; i < tbits.length; i++) {
let val = convertPerfUnit(tbits[i], places);
if(val!=matcher) allMatch = false;
vbits[i].innerHTML = val;
}
if(allMatch) calc(places + 1);
}
calc(0);
});
};
};
}
const Kilobyte = 1024;
const Megabyte = Kilobyte * 1024;
const Gigabyte = Megabyte * 1024;
@ -60,9 +100,30 @@ function convertByteUnit(bytes, places = 0) {
}
}
let ms = 1000;
let sec = ms * 1000;
let min = sec * 60;
let hour = min * 60;
let day = hour * 24;
function convertPerfUnit(quan, places = 0) {
let out;
if(quan >= day) out = [quan / day, "d"];
else if(quan >= hour) out = [quan / hour, "h"];
else if(quan >= min) out = [quan / min, "m"];
else if(quan >= sec) out = [quan / sec, "s"];
else if(quan >= ms) out = [quan / ms, "ms"];
else out = [quan,"μs"];
if(places==0) return Math.ceil(out[0]) + out[1];
else {
let ex = Math.pow(10, places);
return (Math.round(out[0], ex) / ex) + out[1];
}
}
// TODO: Fully localise this
// TODO: Load rawLabels and seriesData dynamically rather than potentially fiddling with nonces for the CSP?
function buildStatsChart(rawLabels, seriesData, timeRange, legendNames, bytes = false) {
function buildStatsChart(rawLabels, seriesData, timeRange, legendNames, typ=0) {
console.log("buildStatsChart");
console.log("seriesData:",seriesData);
let labels = [];
@ -122,7 +183,8 @@ function buildStatsChart(rawLabels, seriesData, timeRange, legendNames, bytes =
if(legendNames.length > 0) config.plugins = [
Chartist.plugins.legend({legendNames: legendNames})
];
if(bytes) config.plugins.push(Chartist.plugins.byteUnits());
if(typ==1) config.plugins.push(Chartist.plugins.byteUnits());
else if(typ==2) config.plugins.push(Chartist.plugins.perfUnits());
Chartist.Line('.ct_chart', {
labels: labels,
series: seriesData,

View File

@ -258,6 +258,7 @@ func panelRoutes() *RouteGroup {
View("panel.AnalyticsActiveMemory", "/panel/analytics/active-memory/").Before("ParseForm"),
View("panel.AnalyticsTopics", "/panel/analytics/topics/").Before("ParseForm"),
View("panel.AnalyticsForums", "/panel/analytics/forums/").Before("ParseForm"),
View("panel.AnalyticsPerf", "/panel/analytics/perf/").Before("ParseForm"),
View("panel.Groups", "/panel/groups/"),
View("panel.GroupsEdit", "/panel/groups/edit/", "extraData"),

View File

@ -8,6 +8,7 @@ import (
"time"
c "github.com/Azareal/Gosora/common"
co "github.com/Azareal/Gosora/common/counters"
)
var successJSONBytes = []byte(`{"success":1}`)
@ -89,7 +90,7 @@ func renderTemplate(tmplName string, w http.ResponseWriter, r *http.Request, hea
return renderTemplate2(tmplName, tmplName, w, r, header, pi)
}
func renderTemplate2(tmplName string, hookName string, w http.ResponseWriter, r *http.Request, header *c.Header, pi interface{}) c.RouteError {
func renderTemplate2(tmplName, hookName string, w http.ResponseWriter, r *http.Request, header *c.Header, pi interface{}) c.RouteError {
err := renderTemplate3(tmplName, tmplName, w, r, header, pi)
if err != nil {
return c.InternalError(err, w, r)
@ -114,7 +115,7 @@ func FootHeaders(w http.ResponseWriter, header *c.Header) {
}
}
func renderTemplate3(tmplName string, hookName string, w http.ResponseWriter, r *http.Request, h *c.Header, pi interface{}) error {
func renderTemplate3(tmplName, hookName string, w http.ResponseWriter, r *http.Request, h *c.Header, pi interface{}) error {
s := h.Stylesheets
h.Stylesheets = nil
c.PrepResources(&h.CurrentUser, h, h.Theme)
@ -134,9 +135,11 @@ func renderTemplate3(tmplName string, hookName string, w http.ResponseWriter, r
}
FootHeaders(w, h)
if h.CurrentUser.IsAdmin {
h.Elapsed1 = time.Since(h.StartedAt).String()
}
since := time.Since(h.StartedAt)
//if h.CurrentUser.IsAdmin {
h.Elapsed1 = since.String()
//}
co.PerfCounter.Push(since)
if c.RunPreRenderHook("pre_render_"+hookName, w, r, &h.CurrentUser, pi) {
return nil
}

View File

@ -11,7 +11,7 @@ import (
c "github.com/Azareal/Gosora/common"
p "github.com/Azareal/Gosora/common/phrases"
"github.com/Azareal/Gosora/query_gen"
qgen "github.com/Azareal/Gosora/query_gen"
)
// TODO: Move this to another file, probably common/pages.go
@ -25,11 +25,11 @@ type AnalyticsTimeRange struct {
func analyticsTimeRange(rawTimeRange string) (*AnalyticsTimeRange, error) {
tRange := &AnalyticsTimeRange{
Quantity: 6,
Unit: "hour",
Slices: 12,
Quantity: 6,
Unit: "hour",
Slices: 12,
SliceWidth: 60 * 30,
Range: "six-hours",
Range: "six-hours",
}
switch rawTimeRange {
@ -198,6 +198,50 @@ func analyticsRowsToAverageMap2(rows *sql.Rows, labelList []int64, avgMap map[in
return avgMap, rows.Err()
}
func analyticsRowsToAverageMap3(rows *sql.Rows, labelList []int64, avgMap map[int64]int64, typ int) (map[int64]int64, error) {
defer rows.Close()
for rows.Next() {
var low, high, avg int64
var createdAt time.Time
err := rows.Scan(&low, &high, &avg, &createdAt)
if err != nil {
return avgMap, err
}
unixCreatedAt := createdAt.Unix()
// TODO: Bulk log this
if c.Dev.SuperDebug {
log.Print("low: ", low)
log.Print("high: ", high)
log.Print("avg: ", avg)
log.Print("createdAt: ", createdAt)
log.Print("unixCreatedAt: ", unixCreatedAt)
}
var dat int64
switch typ {
case 0:
dat = low
case 1:
dat = high
default:
dat = avg
}
pAvgMap := make(map[int64]pAvg)
for _, value := range labelList {
if unixCreatedAt > value {
prev := pAvgMap[value]
prev.Avg += dat
prev.Tot++
pAvgMap[value] = prev
break
}
}
for key, pAvg := range pAvgMap {
avgMap[key] = pAvg.Avg / pAvg.Tot
}
}
return avgMap, rows.Err()
}
func PreAnalyticsDetail(w http.ResponseWriter, r *http.Request, user *c.User) (*c.BasePanelPage, c.RouteError) {
bp, ferr := buildBasePage(w, r, user, "analytics", "analytics")
if ferr != nil {
@ -223,7 +267,7 @@ func AnalyticsViews(w http.ResponseWriter, r *http.Request, user c.User) c.Route
c.DebugLog("in panel.AnalyticsViews")
// TODO: Add some sort of analytics store / iterator?
rows, err := qgen.NewAcc().Select("viewchunks").Columns("count, createdAt").Where("route = ''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
rows, err := qgen.NewAcc().Select("viewchunks").Columns("count,createdAt").Where("route=''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
@ -232,8 +276,8 @@ func AnalyticsViews(w http.ResponseWriter, r *http.Request, user c.User) c.Route
return c.InternalError(err, w, r)
}
viewList := make([]int64,len(revLabelList))
viewItems := make([]c.PanelAnalyticsItem,len(revLabelList))
viewList := make([]int64, len(revLabelList))
viewItems := make([]c.PanelAnalyticsItem, len(revLabelList))
for i, value := range revLabelList {
viewList[i] = viewMap[value]
viewItems[i] = c.PanelAnalyticsItem{Time: value, Count: viewMap[value]}
@ -263,7 +307,7 @@ func AnalyticsRouteViews(w http.ResponseWriter, r *http.Request, user c.User, ro
c.DebugLog("in panel.AnalyticsRouteViews")
// TODO: Validate the route is valid
rows, err := qgen.NewAcc().Select("viewchunks").Columns("count, createdAt").Where("route = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(route)
rows, err := qgen.NewAcc().Select("viewchunks").Columns("count,createdAt").Where("route=?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(route)
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
@ -300,7 +344,7 @@ func AnalyticsAgentViews(w http.ResponseWriter, r *http.Request, user c.User, ag
c.DebugLog("in panel.AnalyticsAgentViews")
// TODO: Verify the agent is valid
rows, err := qgen.NewAcc().Select("viewchunks_agents").Columns("count, createdAt").Where("browser = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(agent)
rows, err := qgen.NewAcc().Select("viewchunks_agents").Columns("count,createdAt").Where("browser=?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(agent)
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
@ -343,7 +387,7 @@ func AnalyticsForumViews(w http.ResponseWriter, r *http.Request, user c.User, sf
c.DebugLog("in panel.AnalyticsForumViews")
// TODO: Verify the agent is valid
rows, err := qgen.NewAcc().Select("viewchunks_forums").Columns("count, createdAt").Where("forum = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(fid)
rows, err := qgen.NewAcc().Select("viewchunks_forums").Columns("count,createdAt").Where("forum=?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(fid)
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
@ -382,7 +426,7 @@ func AnalyticsSystemViews(w http.ResponseWriter, r *http.Request, user c.User, s
c.DebugLog("in panel.AnalyticsSystemViews")
// TODO: Verify the OS name is valid
rows, err := qgen.NewAcc().Select("viewchunks_systems").Columns("count, createdAt").Where("system = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(system)
rows, err := qgen.NewAcc().Select("viewchunks_systems").Columns("count,createdAt").Where("system=?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(system)
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
@ -421,7 +465,7 @@ func AnalyticsLanguageViews(w http.ResponseWriter, r *http.Request, user c.User,
c.DebugLog("in panel.AnalyticsLanguageViews")
// TODO: Verify the language code is valid
rows, err := qgen.NewAcc().Select("viewchunks_langs").Columns("count, createdAt").Where("lang = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(lang)
rows, err := qgen.NewAcc().Select("viewchunks_langs").Columns("count,createdAt").Where("lang=?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(lang)
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
@ -460,7 +504,7 @@ func AnalyticsReferrerViews(w http.ResponseWriter, r *http.Request, user c.User,
c.DebugLog("in panel.AnalyticsReferrerViews")
// TODO: Verify the agent is valid
rows, err := qgen.NewAcc().Select("viewchunks_referrers").Columns("count, createdAt").Where("domain = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(domain)
rows, err := qgen.NewAcc().Select("viewchunks_referrers").Columns("count,createdAt").Where("domain=?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(domain)
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
@ -491,7 +535,7 @@ func AnalyticsTopics(w http.ResponseWriter, r *http.Request, user c.User) c.Rout
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
c.DebugLog("in panel.AnalyticsTopics")
rows, err := qgen.NewAcc().Select("topicchunks").Columns("count, createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
rows, err := qgen.NewAcc().Select("topicchunks").Columns("count,createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
@ -524,7 +568,7 @@ func AnalyticsPosts(w http.ResponseWriter, r *http.Request, user c.User) c.Route
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
c.DebugLog("in panel.AnalyticsPosts")
rows, err := qgen.NewAcc().Select("postchunks").Columns("count, createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
rows, err := qgen.NewAcc().Select("postchunks").Columns("count,createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
@ -557,7 +601,7 @@ func AnalyticsMemory(w http.ResponseWriter, r *http.Request, user c.User) c.Rout
revLabelList, labelList, avgMap := analyticsTimeRangeToLabelList(timeRange)
c.DebugLog("in panel.AnalyticsMemory")
rows, err := qgen.NewAcc().Select("memchunks").Columns("count, createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
rows, err := qgen.NewAcc().Select("memchunks").Columns("count,createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
@ -593,7 +637,7 @@ func AnalyticsActiveMemory(w http.ResponseWriter, r *http.Request, user c.User)
revLabelList, labelList, avgMap := analyticsTimeRangeToLabelList(timeRange)
c.DebugLog("in panel.AnalyticsActiveMemory")
rows, err := qgen.NewAcc().Select("memchunks").Columns("stack, heap, createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
rows, err := qgen.NewAcc().Select("memchunks").Columns("stack,heap,createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
@ -626,6 +670,51 @@ func AnalyticsActiveMemory(w http.ResponseWriter, r *http.Request, user c.User)
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "panel_analytics_right", "analytics", "panel_analytics_active_memory", pi})
}
func AnalyticsPerf(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
basePage, ferr := PreAnalyticsDetail(w, r, &user)
if ferr != nil {
return ferr
}
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
if err != nil {
return c.LocalError(err.Error(), w, r, user)
}
revLabelList, labelList, avgMap := analyticsTimeRangeToLabelList(timeRange)
c.DebugLog("in panel.AnalyticsPerf")
rows, err := qgen.NewAcc().Select("perfchunks").Columns("low,high,avg,createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
var typ int
switch r.FormValue("type") {
case "0":
typ = 0
case "1":
typ = 1
default:
typ = 2
}
avgMap, err = analyticsRowsToAverageMap3(rows, labelList, avgMap, typ)
if err != nil {
return c.InternalError(err, w, r)
}
// TODO: Adjust for the missing chunks in week and month
var avgList []int64
var avgItems []c.PanelAnalyticsItemUnit
for _, value := range revLabelList {
avgList = append(avgList, avgMap[value])
cv, cu := c.ConvertByteUnit(float64(avgMap[value]))
avgItems = append(avgItems, c.PanelAnalyticsItemUnit{Time: value, Unit: cu, Count: int64(cv)})
}
graph := c.PanelTimeGraph{Series: [][]int64{avgList}, Labels: labelList}
c.DebugLogf("graph: %+v\n", graph)
pi := c.PanelAnalyticsPerf{graph, avgItems, timeRange.Range, timeRange.Unit, "time", typ}
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "panel_analytics_right", "analytics", "panel_analytics_performance", pi})
}
func analyticsRowsToRefMap(rows *sql.Rows) (map[string]int, error) {
nameMap := make(map[string]int)
defer rows.Close()
@ -736,7 +825,7 @@ func AnalyticsForums(w http.ResponseWriter, r *http.Request, user c.User) c.Rout
}
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
rows, err := qgen.NewAcc().Select("viewchunks_forums").Columns("count, forum, createdAt").Where("forum != ''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
rows, err := qgen.NewAcc().Select("viewchunks_forums").Columns("count,forum,createdAt").Where("forum!=''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
@ -820,7 +909,7 @@ func AnalyticsRoutes(w http.ResponseWriter, r *http.Request, user c.User) c.Rout
}
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
rows, err := qgen.NewAcc().Select("viewchunks").Columns("count, route, createdAt").Where("route != ''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
rows, err := qgen.NewAcc().Select("viewchunks").Columns("count,route,createdAt").Where("route!=''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
@ -892,7 +981,7 @@ func AnalyticsAgents(w http.ResponseWriter, r *http.Request, user c.User) c.Rout
}
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
rows, err := qgen.NewAcc().Select("viewchunks_agents").Columns("count, browser, createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
rows, err := qgen.NewAcc().Select("viewchunks_agents").Columns("count,browser,createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
@ -978,7 +1067,7 @@ func AnalyticsSystems(w http.ResponseWriter, r *http.Request, user c.User) c.Rou
}
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
rows, err := qgen.NewAcc().Select("viewchunks_systems").Columns("count, system, createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
rows, err := qgen.NewAcc().Select("viewchunks_systems").Columns("count,system,createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
@ -1042,7 +1131,7 @@ func AnalyticsLanguages(w http.ResponseWriter, r *http.Request, user c.User) c.R
}
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
rows, err := qgen.NewAcc().Select("viewchunks_langs").Columns("count, lang, createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
rows, err := qgen.NewAcc().Select("viewchunks_langs").Columns("count,lang,createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
@ -1125,7 +1214,7 @@ func AnalyticsReferrers(w http.ResponseWriter, r *http.Request, user c.User) c.R
return c.LocalError(err.Error(), w, r, user)
}
rows, err := qgen.NewAcc().Select("viewchunks_referrers").Columns("count, domain").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
rows, err := qgen.NewAcc().Select("viewchunks_referrers").Columns("count,domain").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}

View File

@ -38,6 +38,8 @@ func Debug(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
// TODO: Fetch the adapter from Builder rather than getting it from a global?
goroutines := runtime.NumGoroutine()
cpus := runtime.NumCPU()
debugTasks := c.DebugPageTasks{c.ScheduledHalfSecondTaskCount(),c.ScheduledSecondTaskCount(),c.ScheduledFifteenMinuteTaskCount(),c.ScheduledHourTaskCount(),c.ShutdownTaskCount()}
var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)
@ -113,10 +115,11 @@ func Debug(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
if fErr != nil {
return c.InternalError(fErr,w,r)
}
gitSize, _ := c.DirSize("./.git")
//gitSize, _ := c.DirSize("./.git")
gitSize := 0
debugDisk := c.DebugPageDisk{staticSize,attachSize,uploadsSize,logsSize,backupsSize,gitSize}
pi := c.PanelDebugPage{basePage, goVersion, dbVersion, uptime, openConnCount, qgen.Builder.GetAdapter().GetName(), goroutines, cpus, memStats, debugCache, debugDatabase, debugDisk}
pi := c.PanelDebugPage{basePage, goVersion, dbVersion, uptime, openConnCount, qgen.Builder.GetAdapter().GetName(), goroutines, cpus,debugTasks, memStats, debugCache, debugDatabase, debugDisk}
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "panel_dashboard_right", "debug_page", "panel_debug", pi})
}

View File

@ -0,0 +1,6 @@
CREATE TABLE [perfchunks] (
[low] int DEFAULT 0 not null,
[high] int DEFAULT 0 not null,
[avg] int DEFAULT 0 not null,
[createdAt] datetime not null
);

View File

@ -21,5 +21,6 @@ CREATE TABLE [topics] (
[poll] int DEFAULT 0 not null,
[data] nvarchar (200) DEFAULT '' not null,
primary key([tid]),
fulltext key([title]),
fulltext key([content])
);

View File

@ -6,9 +6,6 @@ ALTER TABLE `emails` ADD INDEX `i_uid` (`uid`);;
ALTER TABLE `attachments` ADD INDEX `i_originID` (`originID`);;
ALTER TABLE `attachments` ADD INDEX `i_path` (`path`);;
ALTER TABLE `activity_stream_matches` ADD INDEX `i_watcher` (`watcher`);;
ALTER TABLE `topics` ADD FULLTEXT(`title`);
ALTER TABLE `topics` ADD FULLTEXT(`content`);
ALTER TABLE `replies` ADD FULLTEXT(`content`);
INSERT INTO `sync`(`last_update`) VALUES (UTC_TIMESTAMP());
INSERT INTO `settings`(`name`,`content`,`type`,`constraints`) VALUES ('activation_type','1','list','1-3');
INSERT INTO `settings`(`name`,`content`,`type`) VALUES ('bigpost_min_words','250','int');

View File

@ -0,0 +1,6 @@
CREATE TABLE `perfchunks` (
`low` int DEFAULT 0 not null,
`high` int DEFAULT 0 not null,
`avg` int DEFAULT 0 not null,
`createdAt` datetime not null
);

View File

@ -21,5 +21,6 @@ CREATE TABLE `topics` (
`poll` int DEFAULT 0 not null,
`data` varchar(200) DEFAULT '' not null,
primary key(`tid`),
fulltext key(`title`),
fulltext key(`content`)
) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;

View File

@ -0,0 +1,6 @@
CREATE TABLE "perfchunks" (
`low` int DEFAULT 0 not null,
`high` int DEFAULT 0 not null,
`avg` int DEFAULT 0 not null,
`createdAt` timestamp not null
);

View File

@ -21,5 +21,6 @@ CREATE TABLE "topics" (
`poll` int DEFAULT 0 not null,
`data` varchar (200) DEFAULT '' not null,
primary key(`tid`),
fulltext key(`title`),
fulltext key(`content`)
);

View File

@ -1,10 +1,10 @@
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{lang "panel_statistics_active_memory_head"}}</h1>
<h1>{{lang "panel_stats_active_memory_head"}}</h1>
<select form="timeRangeForm" class="typeSelector to_right autoSubmitRedirect" name="mtype">
<option value="0"{{if eq .MemType 0}} selected{{end}}>{{lang "panel_statistics_memory_type_total"}}</option>
<option value="1"{{if eq .MemType 1}} selected{{end}}>{{lang "panel_statistics_memory_type_stack"}}</option>
<option value="2"{{if eq .MemType 2}} selected{{end}}>{{lang "panel_statistics_memory_type_heap"}}</option>
<option value="0"{{if eq .MemType 0}} selected{{end}}>{{lang "panel_stats_memory_type_total"}}</option>
<option value="1"{{if eq .MemType 1}} selected{{end}}>{{lang "panel_stats_memory_type_stack"}}</option>
<option value="2"{{if eq .MemType 2}} selected{{end}}>{{lang "panel_stats_memory_type_heap"}}</option>
</select>
<noscript><input form="timeRangeForm" type="submit" /></noscript>
{{template "panel_analytics_time_range_month.html" . }}
@ -12,19 +12,19 @@
</div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/active-memory/" method="get"></form>
<div id="panel_analytics_memory" class="colstack_graph_holder">
<div class="ct_chart" aria-label="{{lang "panel_statistics_memory_chart_aria"}}"></div>
<div class="ct_chart" aria-label="{{lang "panel_stats_memory_chart_aria"}}"></div>
</div>
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{lang "panel_statistics_details_head"}}</h1>
<h1>{{lang "panel_stats_details_head"}}</h1>
</div>
</div>
<div id="panel_analytics_memory_table" class="colstack_item rowlist" aria-label="{{lang "panel_statistics_memory_table_aria"}}">
<div id="panel_analytics_memory_table" class="colstack_item rowlist" aria-label="{{lang "panel_stats_memory_table_aria"}}">
{{range .ViewItems}}
<div class="rowitem panel_compactrow editable_parent">
<a class="panel_upshift unix_{{if or (or (or (eq $.TimeRange "six-hours") (eq $.TimeRange "twelve-hours")) (eq $.TimeRange "one-day")) (eq $.TimeRange "two-days")}}to_24_hour_time{{else}}to_date{{end}}">{{.Time}}</a>
<a class="panel_upshift unix_to_{{if or (or (or (eq $.TimeRange "six-hours") (eq $.TimeRange "twelve-hours")) (eq $.TimeRange "one-day")) (eq $.TimeRange "two-days")}}24_hour_time{{else}}date{{end}}">{{.Time}}</a>
<span class="panel_compacttext to_right">{{.Count}}{{.Unit}}</span>
</div>
{{else}}<div class="rowitem passive rowmsg">{{lang "panel_statistics_memory_no_memory"}}</div>{{end}}
{{else}}<div class="rowitem passive rowmsg">{{lang "panel_stats_memory_no_memory"}}</div>{{end}}
</div>
{{template "panel_analytics_script_memory.html" . }}

View File

@ -1,6 +1,6 @@
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{.FriendlyAgent}}{{lang "panel_statistics_views_head_suffix"}}</h1>
<h1>{{.FriendlyAgent}}{{lang "panel_stats_views_head_suffix"}}</h1>
{{template "panel_analytics_time_range.html" . }}
</div>
</div>

View File

@ -1,6 +1,6 @@
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{lang "panel_statistics_user_agents_head"}}</h1>
<h1>{{lang "panel_stats_user_agents_head"}}</h1>
{{template "panel_analytics_time_range.html" . }}
</div>
</div>
@ -12,8 +12,8 @@
{{range .ItemList}}
<div class="rowitem panel_compactrow editable_parent">
<a href="/panel/analytics/agent/{{.Agent}}" class="panel_upshift">{{.FriendlyAgent}}</a>
<span class="panel_compacttext to_right">{{.Count}}{{lang "panel_statistics_views_suffix"}}</span>
<span class="panel_compacttext to_right">{{.Count}}{{lang "panel_stats_views_suffix"}}</span>
</div>
{{else}}<div class="rowitem passive rowmsg">{{lang "panel_statistics_user_agents_no_user_agents"}}</div>{{end}}
{{else}}<div class="rowitem passive rowmsg">{{lang "panel_stats_user_agents_no_user_agents"}}</div>{{end}}
</div>
{{template "panel_analytics_script.html" . }}

View File

@ -1,6 +1,6 @@
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{.FriendlyAgent}}{{lang "panel_statistics_views_head_suffix"}}</h1>
<h1>{{.FriendlyAgent}}{{lang "panel_stats_views_head_suffix"}}</h1>
{{template "panel_analytics_time_range.html" . }}
</div>
</div>

View File

@ -1,6 +1,6 @@
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{lang "panel_statistics_forums_head"}}</h1>
<h1>{{lang "panel_stats_forums_head"}}</h1>
{{template "panel_analytics_time_range.html" . }}
</div>
</div>
@ -12,8 +12,8 @@
{{range .ItemList}}
<div class="rowitem panel_compactrow editable_parent">
<a href="/panel/analytics/forum/{{.Agent}}" class="panel_upshift">{{.FriendlyAgent}}</a>
<span class="panel_compacttext to_right">{{.Count}}{{lang "panel_statistics_views_suffix"}}</span>
<span class="panel_compacttext to_right">{{.Count}}{{lang "panel_stats_views_suffix"}}</span>
</div>
{{else}}<div class="rowitem passive rowmsg">{{lang "panel_statistics_forums_no_forums"}}</div>{{end}}
{{else}}<div class="rowitem passive rowmsg">{{lang "panel_stats_forums_no_forums"}}</div>{{end}}
</div>
{{template "panel_analytics_script.html" . }}

View File

@ -1,6 +1,6 @@
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{.FriendlyAgent}}{{lang "panel_statistics_views_head_suffix"}}</h1>
<h1>{{.FriendlyAgent}}{{lang "panel_stats_views_head_suffix"}}</h1>
{{template "panel_analytics_time_range.html" . }}
</div>
</div>

View File

@ -1,6 +1,6 @@
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{lang "panel_statistics_languages_head"}}</h1>
<h1>{{lang "panel_stats_languages_head"}}</h1>
{{template "panel_analytics_time_range.html" . }}
</div>
</div>
@ -12,8 +12,8 @@
{{range .ItemList}}
<div class="rowitem panel_compactrow editable_parent">
<a href="/panel/analytics/lang/{{.Agent}}" class="panel_upshift">{{.FriendlyAgent}}</a>
<span class="panel_compacttext to_right">{{.Count}}{{lang "panel_statistics_views_suffix"}}</span>
<span class="panel_compacttext to_right">{{.Count}}{{lang "panel_stats_views_suffix"}}</span>
</div>
{{else}}<div class="rowitem passive rowmsg">{{lang "panel_statistics_languages_no_languages"}}</div>{{end}}
{{else}}<div class="rowitem passive rowmsg">{{lang "panel_stats_languages_no_languages"}}</div>{{end}}
</div>
{{template "panel_analytics_script.html" . }}

View File

@ -1,24 +1,24 @@
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{lang "panel_statistics_memory_head"}}</h1>
<h1>{{lang "panel_stats_memory_head"}}</h1>
{{template "panel_analytics_time_range_month.html" . }}
</div>
</div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/memory/" method="get"></form>
<div id="panel_analytics_memory" class="colstack_graph_holder">
<div class="ct_chart" aria-label="{{lang "panel_statistics_memory_chart_aria"}}"></div>
<div class="ct_chart" aria-label="{{lang "panel_stats_memory_chart_aria"}}"></div>
</div>
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{lang "panel_statistics_details_head"}}</h1>
<h1>{{lang "panel_stats_details_head"}}</h1>
</div>
</div>
<div id="panel_analytics_memory_table" class="colstack_item rowlist" aria-label="{{lang "panel_statistics_memory_table_aria"}}">
<div id="panel_analytics_memory_table" class="colstack_item rowlist" aria-label="{{lang "panel_stats_memory_table_aria"}}">
{{range .ViewItems}}
<div class="rowitem panel_compactrow editable_parent">
<a class="panel_upshift unix_{{if or (or (or (eq $.TimeRange "six-hours") (eq $.TimeRange "twelve-hours")) (eq $.TimeRange "one-day")) (eq $.TimeRange "two-days")}}to_24_hour_time{{else}}to_date{{end}}">{{.Time}}</a>
<span class="panel_compacttext to_right">{{.Count}}{{.Unit}}</span>
</div>
{{else}}<div class="rowitem passive rowmsg">{{lang "panel_statistics_memory_no_memory"}}</div>{{end}}
{{else}}<div class="rowitem passive rowmsg">{{lang "panel_stats_memory_no_memory"}}</div>{{end}}
</div>
{{template "panel_analytics_script_memory.html" . }}

View File

@ -0,0 +1,30 @@
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{lang "panel_stats_perf_head"}}</h1>
<select form="timeRangeForm" class="typeSelector to_right autoSubmitRedirect" name="type">
<option value="0"{{if eq .PerfType 0}} selected{{end}}>{{lang "panel_stats_perf_low"}}</option>
<option value="1"{{if eq .PerfType 1}} selected{{end}}>{{lang "panel_stats_perf_high"}}</option>
<option value="2"{{if eq .PerfType 2}} selected{{end}}>{{lang "panel_stats_perf_avg"}}</option>
</select>
<noscript><input form="timeRangeForm" type="submit" /></noscript>
{{template "panel_analytics_time_range_month.html" . }}
</div>
</div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/perf/" method="get"></form>
<div id="panel_analytics_memory" class="colstack_graph_holder">
<div class="ct_chart" aria-label="{{lang "panel_stats_perf_chart_aria"}}"></div>
</div>
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{lang "panel_stats_details_head"}}</h1>
</div>
</div>
<div id="panel_analytics_perf_table" class="colstack_item rowlist" aria-label="{{lang "panel_stats_perf_table_aria"}}">
{{range .ViewItems}}
<div class="rowitem panel_compactrow editable_parent">
<a class="panel_upshift unix_to_{{if or (or (or (eq $.TimeRange "six-hours") (eq $.TimeRange "twelve-hours")) (eq $.TimeRange "one-day")) (eq $.TimeRange "two-days")}}24_hour_time{{else}}date{{end}}">{{.Time}}</a>
<span class="panel_compacttext to_right">{{.Count}}{{.Unit}}</span>
</div>
{{else}}<div class="rowitem passive rowmsg">{{lang "panel_stats_perf_no_perf"}}</div>{{end}}
</div>
{{template "panel_analytics_script_perf.html" . }}

View File

@ -1,24 +1,24 @@
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{lang "panel_statistics_post_counts_head"}}</h1>
<h1>{{lang "panel_stats_post_counts_head"}}</h1>
{{template "panel_analytics_time_range.html" . }}
</div>
</div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/posts/" method="get"></form>
<div id="panel_analytics_posts" class="colstack_graph_holder">
<div class="ct_chart" aria-label="{{lang "panel_statistics_post_counts_chart_aria"}}"></div>
<div class="ct_chart" aria-label="{{lang "panel_stats_post_counts_chart_aria"}}"></div>
</div>
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{lang "panel_statistics_details_head"}}</h1>
<h1>{{lang "panel_stats_details_head"}}</h1>
</div>
</div>
<div id="panel_analytics_posts_table" class="colstack_item rowlist" aria-label="{{lang "panel_statistics_post_counts_table_aria"}}">
<div id="panel_analytics_posts_table" class="colstack_item rowlist" aria-label="{{lang "panel_stats_post_counts_table_aria"}}">
{{range .ViewItems}}
<div class="rowitem panel_compactrow editable_parent">
<a class="panel_upshift unix_{{if or (or (or (eq $.TimeRange "six-hours") (eq $.TimeRange "twelve-hours")) (eq $.TimeRange "one-day")) (eq $.TimeRange "two-days")}}to_24_hour_time{{else}}to_date{{end}}">{{.Time}}</a>
<span class="panel_compacttext to_right">{{.Count}}{{lang "panel_statistics_posts_suffix"}}</span>
<span class="panel_compacttext to_right">{{.Count}}{{lang "panel_stats_posts_suffix"}}</span>
</div>
{{else}}<div class="rowitem passive rowmsg">{{lang "panel_statistics_post_counts_no_post_counts"}}</div>{{end}}
{{else}}<div class="rowitem passive rowmsg">{{lang "panel_stats_post_counts_no_post_counts"}}</div>{{end}}
</div>
{{template "panel_analytics_script.html" . }}

View File

@ -1,6 +1,6 @@
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{.Agent}}{{lang "panel_statistics_views_head_suffix"}}</h1>
<h1>{{.Agent}}{{lang "panel_stats_views_head_suffix"}}</h1>
{{template "panel_analytics_time_range.html" . }}
</div>
</div>

View File

@ -1,9 +1,9 @@
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{lang "panel_statistics_referrers_head"}}</h1>
<h1>{{lang "panel_stats_referrers_head"}}</h1>
<select form="timeRangeForm" class="spamSelector to_right autoSubmitRedirect" name="spam">
<option value="0"{{if not .ShowSpam}} selected{{end}}>{{lang "panel_statistics_spam_hide"}}</option>
<option value="1"{{if .ShowSpam}} selected{{end}}>{{lang "panel_statistics_spam_show"}}</option>
<option value="0"{{if not .ShowSpam}} selected{{end}}>{{lang "panel_stats_spam_hide"}}</option>
<option value="1"{{if .ShowSpam}} selected{{end}}>{{lang "panel_stats_spam_show"}}</option>
</select>
<noscript><input form="timeRangeForm" type="submit" /></noscript>
{{template "panel_analytics_time_range.html" . }}
@ -14,7 +14,7 @@
{{range .ItemList}}
<div class="rowitem panel_compactrow editable_parent">
<a href="/panel/analytics/referrer/{{.Agent}}" class="panel_upshift">{{.Agent}}</a>
<span class="panel_compacttext to_right">{{.Count}}{{lang "panel_statistics_views_suffix"}}</span>
<span class="panel_compacttext to_right">{{.Count}}{{lang "panel_stats_views_suffix"}}</span>
</div>
{{else}}<div class="rowitem passive rowmsg">{{lang "panel_statistics_referrers_no_referrers"}}</div>{{end}}
{{else}}<div class="rowitem passive rowmsg">{{lang "panel_stats_referrers_no_referrers"}}</div>{{end}}
</div>

View File

@ -1,6 +1,6 @@
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{.Route}}{{lang "panel_statistics_views_head_suffix"}}</h1>
<h1>{{.Route}}{{lang "panel_stats_views_head_suffix"}}</h1>
{{template "panel_analytics_time_range.html" . }}
</div>
</div>
@ -9,13 +9,13 @@
<div class="ct_chart"></div>
</div>
<div class="colstack_item colstack_head">
<div class="rowitem"><a>{{lang "panel_statistics_details_head"}}</a></div>
<div class="rowitem"><a>{{lang "panel_stats_details_head"}}</a></div>
</div>
<div id="panel_analytics_views_table" class="colstack_item rowlist" aria-label="{{lang "panel_statistics_route_views_table_aria"}}">
<div id="panel_analytics_views_table" class="colstack_item rowlist" aria-label="{{lang "panel_stats_route_views_table_aria"}}">
{{range .ViewItems}}
<div class="rowitem panel_compactrow editable_parent">
<a class="panel_upshift unix_to_24_hour_time">{{.Time}}</a>
<span class="panel_compacttext to_right">{{.Count}}{{lang "panel_statistics_views_suffix"}}</span>
<span class="panel_compacttext to_right">{{.Count}}{{lang "panel_stats_views_suffix"}}</span>
</div>
{{end}}
</div>

View File

@ -1,6 +1,6 @@
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{lang "panel_statistics_routes_head"}}</h1>
<h1>{{lang "panel_stats_routes_head"}}</h1>
{{template "panel_analytics_time_range.html" . }}
</div>
</div>
@ -12,8 +12,8 @@
{{range .ItemList}}
<div class="rowitem panel_compactrow editable_parent">
<a href="/panel/analytics/route/{{.Route}}" class="panel_upshift">{{.Route}}</a>
<span class="panel_compacttext to_right">{{.Count}}{{lang "panel_statistics_views_suffix"}}</span>
<span class="panel_compacttext to_right">{{.Count}}{{lang "panel_stats_views_suffix"}}</span>
</div>
{{else}}<div class="rowitem passive rowmsg">{{lang "panel_statistics_routes_no_routes"}}</div>{{end}}
{{else}}<div class="rowitem passive rowmsg">{{lang "panel_stats_routes_no_routes"}}</div>{{end}}
</div>
{{template "panel_analytics_script.html" . }}

View File

@ -1,21 +1,21 @@
<script>
let rawLabels = [{{range .Graph.Labels}}
{{.}},{{end}}
];
let seriesData = [{{range .Graph.Series}}[{{range .}}
{{.}},{{end}}
],{{end}}
];
let legendNames = [{{range .Graph.Legends}}
{{.}},{{end}}
];
let rawLabels = [{{range .Graph.Labels}}
{{.}},{{end}}
];
let seriesData = [{{range .Graph.Series}}[{{range .}}
{{.}},{{end}}
],{{end}}
];
let legendNames = [{{range .Graph.Legends}}
{{.}},{{end}}
];
addInitHook("after_phrases", () => {
addInitHook("end_init", () => {
addInitHook("analytics_loaded", () => {
memStuff(window, document, Chartist);
buildStatsChart(rawLabels, seriesData, "{{.TimeRange}}",legendNames,true);
});
addInitHook("after_phrases", () => {
addInitHook("end_init", () => {
addInitHook("analytics_loaded", () => {
memStuff(window, document, Chartist);
buildStatsChart(rawLabels, seriesData, "{{.TimeRange}}",legendNames,1);
});
});
});
</script>

View File

@ -0,0 +1,21 @@
<script>
let rawLabels = [{{range .Graph.Labels}}
{{.}},{{end}}
];
let seriesData = [{{range .Graph.Series}}[{{range .}}
{{.}},{{end}}
],{{end}}
];
let legendNames = [{{range .Graph.Legends}}
{{.}},{{end}}
];
addInitHook("after_phrases", () => {
addInitHook("end_init", () => {
addInitHook("analytics_loaded", () => {
perfStuff(window, document, Chartist);
buildStatsChart(rawLabels, seriesData, "{{.TimeRange}}",legendNames,2);
});
});
});
</script>

View File

@ -1,6 +1,6 @@
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{.FriendlyAgent}}{{lang "panel_statistics_views_head_suffix"}}</h1>
<h1>{{.FriendlyAgent}}{{lang "panel_stats_views_head_suffix"}}</h1>
{{template "panel_analytics_time_range.html" . }}
</div>
</div>

View File

@ -1,6 +1,6 @@
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{lang "panel_statistics_operating_systems_head"}}</h1>
<h1>{{lang "panel_stats_operating_systems_head"}}</h1>
{{template "panel_analytics_time_range.html" . }}
</div>
</div>
@ -12,8 +12,8 @@
{{range .ItemList}}
<div class="rowitem panel_compactrow editable_parent">
<a href="/panel/analytics/system/{{.Agent}}" class="panel_upshift">{{.FriendlyAgent}}</a>
<span class="panel_compacttext to_right">{{.Count}}{{lang "panel_statistics_views_suffix"}}</span>
<span class="panel_compacttext to_right">{{.Count}}{{lang "panel_stats_views_suffix"}}</span>
</div>
{{else}}<div class="rowitem passive rowmsg">{{lang "panel_statistics_operating_systems_no_operating_systems"}}</div>{{end}}
{{else}}<div class="rowitem passive rowmsg">{{lang "panel_stats_operating_systems_no_operating_systems"}}</div>{{end}}
</div>
{{template "panel_analytics_script.html" . }}

View File

@ -1,11 +1,11 @@
<select form="timeRangeForm" class="timeRangeSelector to_right autoSubmitRedirect" name="timeRange">
<option value="one-year"{{if eq .TimeRange "one-year"}} selected{{end}}>{{lang "panel_statistics_time_range_one_year"}}</option>
<option value="three-months"{{if eq .TimeRange "three-months"}} selected{{end}}>{{lang "panel_statistics_time_range_three_months"}}</option>
<option value="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>{{lang "panel_statistics_time_range_one_month"}}</option>
<option value="one-week"{{if eq .TimeRange "one-week"}} selected{{end}}>{{lang "panel_statistics_time_range_one_week"}}</option>
<option value="two-days"{{if eq .TimeRange "two-days"}} selected{{end}}>{{lang "panel_statistics_time_range_two_days"}}</option>
<option value="one-day"{{if eq .TimeRange "one-day"}} selected{{end}}>{{lang "panel_statistics_time_range_one_day"}}</option>
<option value="twelve-hours"{{if eq .TimeRange "twelve-hours"}} selected{{end}}>{{lang "panel_statistics_time_range_twelve_hours"}}</option>
<option value="six-hours"{{if eq .TimeRange "six-hours"}} selected{{end}}>{{lang "panel_statistics_time_range_six_hours"}}</option>
<option value="one-year"{{if eq .TimeRange "one-year"}} selected{{end}}>{{lang "panel_stats_time_range_one_year"}}</option>
<option value="three-months"{{if eq .TimeRange "three-months"}} selected{{end}}>{{lang "panel_stats_time_range_three_months"}}</option>
<option value="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>{{lang "panel_stats_time_range_one_month"}}</option>
<option value="one-week"{{if eq .TimeRange "one-week"}} selected{{end}}>{{lang "panel_stats_time_range_one_week"}}</option>
<option value="two-days"{{if eq .TimeRange "two-days"}} selected{{end}}>{{lang "panel_stats_time_range_two_days"}}</option>
<option value="one-day"{{if eq .TimeRange "one-day"}} selected{{end}}>{{lang "panel_stats_time_range_one_day"}}</option>
<option value="twelve-hours"{{if eq .TimeRange "twelve-hours"}} selected{{end}}>{{lang "panel_stats_time_range_twelve_hours"}}</option>
<option value="six-hours"{{if eq .TimeRange "six-hours"}} selected{{end}}>{{lang "panel_stats_time_range_six_hours"}}</option>
</select>
<noscript><input form="timeRangeForm" type="submit" /></noscript>

View File

@ -1,9 +1,9 @@
<select form="timeRangeForm" class="timeRangeSelector to_right autoSubmitRedirect" name="timeRange">
<option value="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>{{lang "panel_statistics_time_range_one_month"}}</option>
<option value="one-week"{{if eq .TimeRange "one-week"}} selected{{end}}>{{lang "panel_statistics_time_range_one_week"}}</option>
<option value="two-days"{{if eq .TimeRange "two-days"}} selected{{end}}>{{lang "panel_statistics_time_range_two_days"}}</option>
<option value="one-day"{{if eq .TimeRange "one-day"}} selected{{end}}>{{lang "panel_statistics_time_range_one_day"}}</option>
<option value="twelve-hours"{{if eq .TimeRange "twelve-hours"}} selected{{end}}>{{lang "panel_statistics_time_range_twelve_hours"}}</option>
<option value="six-hours"{{if eq .TimeRange "six-hours"}} selected{{end}}>{{lang "panel_statistics_time_range_six_hours"}}</option>
<option value="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>{{lang "panel_stats_time_range_one_month"}}</option>
<option value="one-week"{{if eq .TimeRange "one-week"}} selected{{end}}>{{lang "panel_stats_time_range_one_week"}}</option>
<option value="two-days"{{if eq .TimeRange "two-days"}} selected{{end}}>{{lang "panel_stats_time_range_two_days"}}</option>
<option value="one-day"{{if eq .TimeRange "one-day"}} selected{{end}}>{{lang "panel_stats_time_range_one_day"}}</option>
<option value="twelve-hours"{{if eq .TimeRange "twelve-hours"}} selected{{end}}>{{lang "panel_stats_time_range_twelve_hours"}}</option>
<option value="six-hours"{{if eq .TimeRange "six-hours"}} selected{{end}}>{{lang "panel_stats_time_range_six_hours"}}</option>
</select>
<noscript><input form="timeRangeForm" type="submit" /></noscript>

View File

@ -1,23 +1,23 @@
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{lang "panel_statistics_topic_counts_head"}}</h1>
<h1>{{lang "panel_stats_topic_counts_head"}}</h1>
{{template "panel_analytics_time_range.html" . }}
</div>
</div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/topics/" method="get"></form>
<div id="panel_analytics_topics" class="colstack_graph_holder">
<div class="ct_chart" aria-label="{{lang "panel_statistics_topic_counts_chart_aria"}}"></div>
<div class="ct_chart" aria-label="{{lang "panel_stats_topic_counts_chart_aria"}}"></div>
</div>
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{lang "panel_statistics_details_head"}}</h1>
<h1>{{lang "panel_stats_details_head"}}</h1>
</div>
</div>
<div id="panel_analytics_topics_table" class="colstack_item rowlist" aria-label="{{lang "panel_statistics_topic_counts_table_aria"}}">
<div id="panel_analytics_topics_table" class="colstack_item rowlist" aria-label="{{lang "panel_stats_topic_counts_table_aria"}}">
{{range .ViewItems}}
<div class="rowitem panel_compactrow editable_parent">
<a class="panel_upshift unix_{{if or (or (or (eq $.TimeRange "six-hours") (eq $.TimeRange "twelve-hours")) (eq $.TimeRange "one-day")) (eq $.TimeRange "two-days")}}to_24_hour_time{{else}}to_date{{end}}">{{.Time}}</a>
<span class="panel_compacttext to_right">{{.Count}}{{lang "panel_statistics_topics_suffix"}}</span>
<a class="panel_upshift unix_to_{{if or (or (or (eq $.TimeRange "six-hours") (eq $.TimeRange "twelve-hours")) (eq $.TimeRange "one-day")) (eq $.TimeRange "two-days")}}24_hour_time{{else}}date{{end}}">{{.Time}}</a>
<span class="panel_compacttext to_right">{{.Count}}{{lang "panel_stats_topics_suffix"}}</span>
</div>
{{end}}
</div>

View File

@ -1,23 +1,23 @@
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{lang "panel_statistics_requests_head"}}</h1>
<h1>{{lang "panel_stats_requests_head"}}</h1>
{{template "panel_analytics_time_range.html" . }}
</div>
</div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/views/" method="get"></form>
<div id="panel_analytics_views" class="colstack_graph_holder">
<div class="ct_chart" aria-label="{{lang "panel_statistics_requests_chart_aria"}}"></div>
<div class="ct_chart" aria-label="{{lang "panel_stats_requests_chart_aria"}}"></div>
</div>
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{lang "panel_statistics_details_head"}}</h1>
<h1>{{lang "panel_stats_details_head"}}</h1>
</div>
</div>
<div id="panel_analytics_views_table" class="colstack_item rowlist" aria-label="{{lang "panel_statistics_requests_table_aria"}}">
<div id="panel_analytics_views_table" class="colstack_item rowlist" aria-label="{{lang "panel_stats_requests_table_aria"}}">
{{range .ViewItems}}
<div class="rowitem panel_compactrow editable_parent">
<a class="panel_upshift unix_{{if or (or (or (eq $.TimeRange "six-hours") (eq $.TimeRange "twelve-hours")) (eq $.TimeRange "one-day")) (eq $.TimeRange "two-days")}}to_24_hour_time{{else}}to_date{{end}}">{{.Time}}</a>
<span class="panel_compacttext to_right">{{.Count}}{{lang "panel_statistics_views_suffix"}}</span>
<a class="panel_upshift unix_to_{{if or (or (or (eq $.TimeRange "six-hours") (eq $.TimeRange "twelve-hours")) (eq $.TimeRange "one-day")) (eq $.TimeRange "two-days")}}24_hour_time{{else}}date{{end}}">{{.Time}}</a>
<span class="panel_compacttext to_right">{{.Count}}{{lang "panel_stats_views_suffix"}}</span>
</div>
{{end}}
</div>

View File

@ -24,6 +24,22 @@
{{template "panel_debug_stat.html" .CPUs}}
{{template "panel_debug_stat_q.html"}}
</div>
{{template "panel_debug_subhead.html" "panel_debug_tasks"}}
<div id="panel_debug" class="colstack_grid">
{{template "panel_debug_stat_head.html" "panel_debug_tasks_half_second"}}
{{template "panel_debug_stat_head.html" "panel_debug_tasks_second"}}
{{template "panel_debug_stat_head.html" "panel_debug_tasks_fifteen_minute"}}
{{template "panel_debug_stat.html" .Tasks.HalfSecond}}
{{template "panel_debug_stat.html" .Tasks.Second}}
{{template "panel_debug_stat.html" .Tasks.FifteenMinute}}
{{template "panel_debug_stat_head.html" "panel_debug_tasks_hour"}}
{{template "panel_debug_stat_head.html" "panel_debug_tasks_shutdown"}}
{{template "panel_debug_stat_head_q.html"}}
{{template "panel_debug_stat.html" .Tasks.Hour}}
{{template "panel_debug_stat.html" .Tasks.Shutdown}}
{{template "panel_debug_stat_q.html"}}
</div>
{{template "panel_debug_subhead.html" "panel_debug_memory_stats"}}
<div id="panel_debug" class="colstack_grid">
{{template "panel_debug_stat_head.html" "panel_debug_memory_stats_sys"}}

View File

@ -35,38 +35,41 @@
</div>
<div class="colstack_item rowmenu">
<div class="rowitem passive">
<a href="/panel/analytics/views/">{{lang "panel_menu_statistics"}}</a>
<a href="/panel/analytics/views/">{{lang "panel_menu_stats"}}</a>
</div>
{{if eq .Zone "analytics"}}
<div class="rowitem passive submenu">
<a href="/panel/analytics/posts/">{{lang "panel_menu_statistics_posts"}}</a>
<a href="/panel/analytics/posts/">{{lang "panel_menu_stats_posts"}}</a>
</div>
<div class="rowitem passive submenu">
<a href="/panel/analytics/topics/">{{lang "panel_menu_statistics_topics"}}</a>
<a href="/panel/analytics/topics/">{{lang "panel_menu_stats_topics"}}</a>
</div>
<div class="rowitem passive submenu">
<a href="/panel/analytics/forums/">{{lang "panel_menu_statistics_forums"}}</a>
<a href="/panel/analytics/forums/">{{lang "panel_menu_stats_forums"}}</a>
</div>
<div class="rowitem passive submenu">
<a href="/panel/analytics/routes/">{{lang "panel_menu_statistics_routes"}}</a>
<a href="/panel/analytics/routes/">{{lang "panel_menu_stats_routes"}}</a>
</div>
<div class="rowitem passive submenu">
<a href="/panel/analytics/agents/">{{lang "panel_menu_statistics_agents"}}</a>
<a href="/panel/analytics/agents/">{{lang "panel_menu_stats_agents"}}</a>
</div>
<div class="rowitem passive submenu">
<a href="/panel/analytics/systems/">{{lang "panel_menu_statistics_systems"}}</a>
<a href="/panel/analytics/systems/">{{lang "panel_menu_stats_systems"}}</a>
</div>
<div class="rowitem passive submenu">
<a href="/panel/analytics/langs/">{{lang "panel_menu_statistics_languages"}}</a>
<a href="/panel/analytics/langs/">{{lang "panel_menu_stats_languages"}}</a>
</div>
<div class="rowitem passive submenu">
<a href="/panel/analytics/referrers/">{{lang "panel_menu_statistics_referrers"}}</a>
<a href="/panel/analytics/referrers/">{{lang "panel_menu_stats_referrers"}}</a>
</div>
<div class="rowitem passive submenu">
<a href="/panel/analytics/memory/">{{lang "panel_menu_statistics_memory"}}</a>
<a href="/panel/analytics/memory/">{{lang "panel_menu_stats_memory"}}</a>
</div>
<div class="rowitem passive submenu">
<a href="/panel/analytics/active-memory/">{{lang "panel_menu_statistics_active_memory"}}</a>
<a href="/panel/analytics/active-memory/">{{lang "panel_menu_stats_active_memory"}}</a>
</div>
<div class="rowitem passive submenu">
<a href="/panel/analytics/perf/">{{lang "panel_menu_stats_perf"}}</a>
</div>
{{end}}
<div class="rowitem passive">

View File

@ -35,38 +35,41 @@
</div>
<div class="colstack_item rowmenu">
<div class="rowitem passive">
<a href="/panel/analytics/views/">{{lang "panel_menu_statistics"}}</a>
<a href="/panel/analytics/views/">{{lang "panel_menu_stats"}}</a>
</div>
{{if eq .Zone "analytics"}}
<div class="rowitem passive submenu">
<a href="/panel/analytics/posts/">{{lang "panel_menu_statistics_posts"}}</a>
<a href="/panel/analytics/posts/">{{lang "panel_menu_stats_posts"}}</a>
</div>
<div class="rowitem passive submenu">
<a href="/panel/analytics/topics/">{{lang "panel_menu_statistics_topics"}}</a>
<a href="/panel/analytics/topics/">{{lang "panel_menu_stats_topics"}}</a>
</div>
<div class="rowitem passive submenu">
<a href="/panel/analytics/forums/">{{lang "panel_menu_statistics_forums"}}</a>
<a href="/panel/analytics/forums/">{{lang "panel_menu_stats_forums"}}</a>
</div>
<div class="rowitem passive submenu">
<a href="/panel/analytics/routes/">{{lang "panel_menu_statistics_routes"}}</a>
<a href="/panel/analytics/routes/">{{lang "panel_menu_stats_routes"}}</a>
</div>
<div class="rowitem passive submenu">
<a href="/panel/analytics/agents/">{{lang "panel_menu_statistics_agents"}}</a>
<a href="/panel/analytics/agents/">{{lang "panel_menu_stats_agents"}}</a>
</div>
<div class="rowitem passive submenu">
<a href="/panel/analytics/systems/">{{lang "panel_menu_statistics_systems"}}</a>
<a href="/panel/analytics/systems/">{{lang "panel_menu_stats_systems"}}</a>
</div>
<div class="rowitem passive submenu">
<a href="/panel/analytics/langs/">{{lang "panel_menu_statistics_languages"}}</a>
<a href="/panel/analytics/langs/">{{lang "panel_menu_stats_languages"}}</a>
</div>
<div class="rowitem passive submenu">
<a href="/panel/analytics/referrers/">{{lang "panel_menu_statistics_referrers"}}</a>
<a href="/panel/analytics/referrers/">{{lang "panel_menu_stats_referrers"}}</a>
</div>
<div class="rowitem passive submenu">
<a href="/panel/analytics/memory/">{{lang "panel_menu_statistics_memory"}}</a>
<a href="/panel/analytics/memory/">{{lang "panel_menu_stats_memory"}}</a>
</div>
<div class="rowitem passive submenu">
<a href="/panel/analytics/active-memory/">{{lang "panel_menu_statistics_active_memory"}}</a>
<a href="/panel/analytics/active-memory/">{{lang "panel_menu_stats_active_memory"}}</a>
</div>
<div class="rowitem passive submenu">
<a href="/panel/analytics/perf/">{{lang "panel_menu_stats_perf"}}</a>
</div>
{{end}}
<div class="rowitem passive">