2014-03-20 15:50:26 +04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2019-06-26 19:12:38 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2014-03-20 15:50:26 +04:00
package admin
import (
2014-03-22 15:42:24 +04:00
"fmt"
2021-04-05 18:30:52 +03:00
"net/http"
2014-03-22 15:42:24 +04:00
"runtime"
2020-01-07 14:23:09 +03:00
"strconv"
2014-03-22 15:42:24 +04:00
"time"
2014-03-21 11:27:59 +04:00
2022-08-25 05:31:57 +03:00
activities_model "code.gitea.io/gitea/models/activities"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
2019-06-26 19:12:38 +03:00
"code.gitea.io/gitea/modules/log"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/process"
2020-01-07 14:23:09 +03:00
"code.gitea.io/gitea/modules/queue"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/setting"
2021-10-21 19:10:49 +03:00
"code.gitea.io/gitea/modules/updatechecker"
2021-01-26 18:36:53 +03:00
"code.gitea.io/gitea/modules/web"
2021-11-16 16:30:11 +03:00
"code.gitea.io/gitea/services/cron"
2021-04-06 22:44:05 +03:00
"code.gitea.io/gitea/services/forms"
2014-03-20 15:50:26 +04:00
)
2014-06-22 21:14:03 +04:00
const (
2022-03-31 20:01:43 +03:00
tplDashboard base . TplName = "admin/dashboard"
tplMonitor base . TplName = "admin/monitor"
tplStacktrace base . TplName = "admin/stacktrace"
tplQueue base . TplName = "admin/queue"
2014-06-22 21:14:03 +04:00
)
2014-03-22 15:42:24 +04:00
var sysStatus struct {
2023-04-11 02:01:20 +03:00
StartTime string
2014-03-22 15:42:24 +04:00
NumGoroutine int
// General statistics.
MemAllocated string // bytes allocated and still in use
MemTotal string // bytes allocated (even if freed)
MemSys string // bytes obtained from system (sum of XxxSys below)
Lookups uint64 // number of pointer lookups
MemMallocs uint64 // number of mallocs
MemFrees uint64 // number of frees
// Main allocation heap statistics.
HeapAlloc string // bytes allocated and still in use
HeapSys string // bytes obtained from system
HeapIdle string // bytes in idle spans
HeapInuse string // bytes in non-idle span
HeapReleased string // bytes released to the OS
HeapObjects uint64 // total number of allocated objects
// Low-level fixed-size structure allocator statistics.
// Inuse is bytes used now.
// Sys is bytes obtained from system.
StackInuse string // bootstrap stacks
StackSys string
MSpanInuse string // mspan structures
MSpanSys string
MCacheInuse string // mcache structures
MCacheSys string
BuckHashSys string // profiling bucket hash table
GCSys string // GC metadata
OtherSys string // other system allocations
// Garbage collector statistics.
NextGC string // next run in HeapAlloc time (bytes)
LastGC string // last run in absolute time (ns)
PauseTotalNs string
PauseNs string // circular buffer of recent GC pause times, most recent at [(NumGC+255)%256]
NumGC uint32
}
func updateSystemStatus ( ) {
2023-04-11 02:01:20 +03:00
sysStatus . StartTime = setting . AppStartTime . Format ( time . RFC3339 )
2014-03-22 17:21:57 +04:00
2014-03-22 15:42:24 +04:00
m := new ( runtime . MemStats )
runtime . ReadMemStats ( m )
sysStatus . NumGoroutine = runtime . NumGoroutine ( )
sysStatus . MemAllocated = base . FileSize ( int64 ( m . Alloc ) )
sysStatus . MemTotal = base . FileSize ( int64 ( m . TotalAlloc ) )
sysStatus . MemSys = base . FileSize ( int64 ( m . Sys ) )
sysStatus . Lookups = m . Lookups
sysStatus . MemMallocs = m . Mallocs
sysStatus . MemFrees = m . Frees
sysStatus . HeapAlloc = base . FileSize ( int64 ( m . HeapAlloc ) )
sysStatus . HeapSys = base . FileSize ( int64 ( m . HeapSys ) )
sysStatus . HeapIdle = base . FileSize ( int64 ( m . HeapIdle ) )
sysStatus . HeapInuse = base . FileSize ( int64 ( m . HeapInuse ) )
sysStatus . HeapReleased = base . FileSize ( int64 ( m . HeapReleased ) )
sysStatus . HeapObjects = m . HeapObjects
sysStatus . StackInuse = base . FileSize ( int64 ( m . StackInuse ) )
sysStatus . StackSys = base . FileSize ( int64 ( m . StackSys ) )
sysStatus . MSpanInuse = base . FileSize ( int64 ( m . MSpanInuse ) )
sysStatus . MSpanSys = base . FileSize ( int64 ( m . MSpanSys ) )
sysStatus . MCacheInuse = base . FileSize ( int64 ( m . MCacheInuse ) )
sysStatus . MCacheSys = base . FileSize ( int64 ( m . MCacheSys ) )
sysStatus . BuckHashSys = base . FileSize ( int64 ( m . BuckHashSys ) )
sysStatus . GCSys = base . FileSize ( int64 ( m . GCSys ) )
sysStatus . OtherSys = base . FileSize ( int64 ( m . OtherSys ) )
sysStatus . NextGC = base . FileSize ( int64 ( m . NextGC ) )
sysStatus . LastGC = fmt . Sprintf ( "%.1fs" , float64 ( time . Now ( ) . UnixNano ( ) - int64 ( m . LastGC ) ) / 1000 / 1000 / 1000 )
2014-03-22 17:21:57 +04:00
sysStatus . PauseTotalNs = fmt . Sprintf ( "%.1fs" , float64 ( m . PauseTotalNs ) / 1000 / 1000 / 1000 )
sysStatus . PauseNs = fmt . Sprintf ( "%.3fs" , float64 ( m . PauseNs [ ( m . NumGC + 255 ) % 256 ] ) / 1000 / 1000 / 1000 )
2014-03-22 15:42:24 +04:00
sysStatus . NumGC = m . NumGC
}
2016-11-21 06:21:24 +03:00
// Dashboard show admin panel dashboard
2016-03-11 19:56:52 +03:00
func Dashboard ( ctx * context . Context ) {
2014-08-28 18:29:00 +04:00
ctx . Data [ "Title" ] = ctx . Tr ( "admin.dashboard" )
ctx . Data [ "PageIsAdminDashboard" ] = true
2022-08-25 05:31:57 +03:00
ctx . Data [ "Stats" ] = activities_model . GetStatistic ( )
2021-10-21 19:10:49 +03:00
ctx . Data [ "NeedUpdate" ] = updatechecker . GetNeedUpdate ( )
ctx . Data [ "RemoteVersion" ] = updatechecker . GetRemoteVersion ( )
2020-02-26 01:54:13 +03:00
// FIXME: update periodically
updateSystemStatus ( )
ctx . Data [ "SysStatus" ] = sysStatus
2020-10-08 19:43:15 +03:00
ctx . Data [ "SSH" ] = setting . SSH
2021-04-05 18:30:52 +03:00
ctx . HTML ( http . StatusOK , tplDashboard )
2020-02-26 01:54:13 +03:00
}
// DashboardPost run an admin operation
2021-01-26 18:36:53 +03:00
func DashboardPost ( ctx * context . Context ) {
2021-04-06 22:44:05 +03:00
form := web . GetForm ( ctx ) . ( * forms . AdminDashboardForm )
2020-02-26 01:54:13 +03:00
ctx . Data [ "Title" ] = ctx . Tr ( "admin.dashboard" )
ctx . Data [ "PageIsAdminDashboard" ] = true
2022-08-25 05:31:57 +03:00
ctx . Data [ "Stats" ] = activities_model . GetStatistic ( )
2020-02-26 01:54:13 +03:00
updateSystemStatus ( )
ctx . Data [ "SysStatus" ] = sysStatus
2014-05-06 21:47:47 +04:00
// Run operation.
2020-05-17 02:31:38 +03:00
if form . Op != "" {
task := cron . GetTask ( form . Op )
if task != nil {
2022-03-22 10:03:22 +03:00
go task . RunWithUser ( ctx . Doer , nil )
2020-05-17 02:31:38 +03:00
ctx . Flash . Success ( ctx . Tr ( "admin.dashboard.task.started" , ctx . Tr ( "admin.dashboard." + form . Op ) ) )
2014-05-06 21:47:47 +04:00
} else {
2020-05-17 02:31:38 +03:00
ctx . Flash . Error ( ctx . Tr ( "admin.dashboard.task.unknown" , form . Op ) )
2014-05-06 21:47:47 +04:00
}
}
2020-07-05 22:38:03 +03:00
if form . From == "monitor" {
ctx . Redirect ( setting . AppSubURL + "/admin/monitor" )
} else {
ctx . Redirect ( setting . AppSubURL + "/admin" )
}
2014-03-20 15:50:26 +04:00
}
2016-11-21 06:21:24 +03:00
// Monitor show admin monitor page
2016-03-11 19:56:52 +03:00
func Monitor ( ctx * context . Context ) {
2014-08-30 16:49:51 +04:00
ctx . Data [ "Title" ] = ctx . Tr ( "admin.monitor" )
ctx . Data [ "PageIsAdminMonitor" ] = true
2022-03-31 20:01:43 +03:00
ctx . Data [ "Processes" ] , ctx . Data [ "ProcessCount" ] = process . GetManager ( ) . Processes ( false , true )
2015-08-17 21:19:29 +03:00
ctx . Data [ "Entries" ] = cron . ListTasks ( )
2020-01-07 14:23:09 +03:00
ctx . Data [ "Queues" ] = queue . GetManager ( ) . ManagedQueues ( )
2022-03-31 20:01:43 +03:00
2021-04-05 18:30:52 +03:00
ctx . HTML ( http . StatusOK , tplMonitor )
2014-06-13 21:01:52 +04:00
}
2019-11-30 17:40:22 +03:00
2022-03-31 20:01:43 +03:00
// GoroutineStacktrace show admin monitor goroutines page
func GoroutineStacktrace ( ctx * context . Context ) {
ctx . Data [ "Title" ] = ctx . Tr ( "admin.monitor" )
ctx . Data [ "PageIsAdminMonitor" ] = true
processStacks , processCount , goroutineCount , err := process . GetManager ( ) . ProcessStacktraces ( false , false )
if err != nil {
ctx . ServerError ( "GoroutineStacktrace" , err )
return
}
ctx . Data [ "ProcessStacks" ] = processStacks
ctx . Data [ "GoroutineCount" ] = goroutineCount
ctx . Data [ "ProcessCount" ] = processCount
ctx . HTML ( http . StatusOK , tplStacktrace )
}
2019-11-30 17:40:22 +03:00
// MonitorCancel cancels a process
func MonitorCancel ( ctx * context . Context ) {
2021-11-30 23:06:32 +03:00
pid := ctx . Params ( "pid" )
process . GetManager ( ) . Cancel ( process . IDType ( pid ) )
2021-04-05 18:30:52 +03:00
ctx . JSON ( http . StatusOK , map [ string ] interface { } {
2020-07-05 22:38:03 +03:00
"redirect" : setting . AppSubURL + "/admin/monitor" ,
2019-11-30 17:40:22 +03:00
} )
}
2020-01-07 14:23:09 +03:00
// Queue shows details for a specific queue
func Queue ( ctx * context . Context ) {
qid := ctx . ParamsInt64 ( "qid" )
mq := queue . GetManager ( ) . GetManagedQueue ( qid )
if mq == nil {
2022-03-23 07:54:07 +03:00
ctx . Status ( http . StatusNotFound )
2020-01-07 14:23:09 +03:00
return
}
ctx . Data [ "Title" ] = ctx . Tr ( "admin.monitor.queue" , mq . Name )
ctx . Data [ "PageIsAdminMonitor" ] = true
ctx . Data [ "Queue" ] = mq
2021-04-05 18:30:52 +03:00
ctx . HTML ( http . StatusOK , tplQueue )
2020-01-07 14:23:09 +03:00
}
// WorkerCancel cancels a worker group
func WorkerCancel ( ctx * context . Context ) {
qid := ctx . ParamsInt64 ( "qid" )
mq := queue . GetManager ( ) . GetManagedQueue ( qid )
if mq == nil {
2022-03-23 07:54:07 +03:00
ctx . Status ( http . StatusNotFound )
2020-01-07 14:23:09 +03:00
return
}
pid := ctx . ParamsInt64 ( "pid" )
mq . CancelWorkers ( pid )
ctx . Flash . Info ( ctx . Tr ( "admin.monitor.queue.pool.cancelling" ) )
2021-04-05 18:30:52 +03:00
ctx . JSON ( http . StatusOK , map [ string ] interface { } {
2021-02-20 00:36:43 +03:00
"redirect" : setting . AppSubURL + "/admin/monitor/queue/" + strconv . FormatInt ( qid , 10 ) ,
2020-01-07 14:23:09 +03:00
} )
}
2020-01-29 04:01:06 +03:00
// Flush flushes a queue
func Flush ( ctx * context . Context ) {
qid := ctx . ParamsInt64 ( "qid" )
mq := queue . GetManager ( ) . GetManagedQueue ( qid )
if mq == nil {
2022-03-23 07:54:07 +03:00
ctx . Status ( http . StatusNotFound )
2020-01-29 04:01:06 +03:00
return
}
2021-08-11 03:31:13 +03:00
timeout , err := time . ParseDuration ( ctx . FormString ( "timeout" ) )
2020-01-29 04:01:06 +03:00
if err != nil {
timeout = - 1
}
ctx . Flash . Info ( ctx . Tr ( "admin.monitor.queue.pool.flush.added" , mq . Name ) )
go func ( ) {
err := mq . Flush ( timeout )
if err != nil {
log . Error ( "Flushing failure for %s: Error %v" , mq . Name , err )
}
} ( )
2021-02-20 00:36:43 +03:00
ctx . Redirect ( setting . AppSubURL + "/admin/monitor/queue/" + strconv . FormatInt ( qid , 10 ) )
2022-01-23 00:22:14 +03:00
}
// Pause pauses a queue
func Pause ( ctx * context . Context ) {
qid := ctx . ParamsInt64 ( "qid" )
mq := queue . GetManager ( ) . GetManagedQueue ( qid )
if mq == nil {
ctx . Status ( 404 )
return
}
mq . Pause ( )
ctx . Redirect ( setting . AppSubURL + "/admin/monitor/queue/" + strconv . FormatInt ( qid , 10 ) )
}
// Resume resumes a queue
func Resume ( ctx * context . Context ) {
qid := ctx . ParamsInt64 ( "qid" )
mq := queue . GetManager ( ) . GetManagedQueue ( qid )
if mq == nil {
ctx . Status ( 404 )
return
}
mq . Resume ( )
ctx . Redirect ( setting . AppSubURL + "/admin/monitor/queue/" + strconv . FormatInt ( qid , 10 ) )
2020-01-29 04:01:06 +03:00
}
2020-01-07 14:23:09 +03:00
// AddWorkers adds workers to a worker group
func AddWorkers ( ctx * context . Context ) {
qid := ctx . ParamsInt64 ( "qid" )
mq := queue . GetManager ( ) . GetManagedQueue ( qid )
if mq == nil {
2022-03-23 07:54:07 +03:00
ctx . Status ( http . StatusNotFound )
2020-01-07 14:23:09 +03:00
return
}
2021-07-29 04:42:15 +03:00
number := ctx . FormInt ( "number" )
2020-01-07 14:23:09 +03:00
if number < 1 {
ctx . Flash . Error ( ctx . Tr ( "admin.monitor.queue.pool.addworkers.mustnumbergreaterzero" ) )
2021-02-20 00:36:43 +03:00
ctx . Redirect ( setting . AppSubURL + "/admin/monitor/queue/" + strconv . FormatInt ( qid , 10 ) )
2020-01-07 14:23:09 +03:00
return
}
2021-08-11 03:31:13 +03:00
timeout , err := time . ParseDuration ( ctx . FormString ( "timeout" ) )
2020-01-07 14:23:09 +03:00
if err != nil {
ctx . Flash . Error ( ctx . Tr ( "admin.monitor.queue.pool.addworkers.musttimeoutduration" ) )
2021-02-20 00:36:43 +03:00
ctx . Redirect ( setting . AppSubURL + "/admin/monitor/queue/" + strconv . FormatInt ( qid , 10 ) )
2020-01-07 14:23:09 +03:00
return
}
2020-01-29 04:01:06 +03:00
if _ , ok := mq . Managed . ( queue . ManagedPool ) ; ! ok {
2020-01-07 14:23:09 +03:00
ctx . Flash . Error ( ctx . Tr ( "admin.monitor.queue.pool.none" ) )
2021-02-20 00:36:43 +03:00
ctx . Redirect ( setting . AppSubURL + "/admin/monitor/queue/" + strconv . FormatInt ( qid , 10 ) )
2020-01-07 14:23:09 +03:00
return
}
mq . AddWorkers ( number , timeout )
ctx . Flash . Success ( ctx . Tr ( "admin.monitor.queue.pool.added" ) )
2021-02-20 00:36:43 +03:00
ctx . Redirect ( setting . AppSubURL + "/admin/monitor/queue/" + strconv . FormatInt ( qid , 10 ) )
2020-01-07 14:23:09 +03:00
}
// SetQueueSettings sets the maximum number of workers and other settings for this queue
func SetQueueSettings ( ctx * context . Context ) {
qid := ctx . ParamsInt64 ( "qid" )
mq := queue . GetManager ( ) . GetManagedQueue ( qid )
if mq == nil {
2022-03-23 07:54:07 +03:00
ctx . Status ( http . StatusNotFound )
2020-01-07 14:23:09 +03:00
return
}
2020-01-29 04:01:06 +03:00
if _ , ok := mq . Managed . ( queue . ManagedPool ) ; ! ok {
2020-01-07 14:23:09 +03:00
ctx . Flash . Error ( ctx . Tr ( "admin.monitor.queue.pool.none" ) )
2021-02-20 00:36:43 +03:00
ctx . Redirect ( setting . AppSubURL + "/admin/monitor/queue/" + strconv . FormatInt ( qid , 10 ) )
2020-01-07 14:23:09 +03:00
return
}
2021-08-11 03:31:13 +03:00
maxNumberStr := ctx . FormString ( "max-number" )
numberStr := ctx . FormString ( "number" )
timeoutStr := ctx . FormString ( "timeout" )
2020-01-07 14:23:09 +03:00
var err error
var maxNumber , number int
var timeout time . Duration
if len ( maxNumberStr ) > 0 {
maxNumber , err = strconv . Atoi ( maxNumberStr )
if err != nil {
ctx . Flash . Error ( ctx . Tr ( "admin.monitor.queue.settings.maxnumberworkers.error" ) )
2021-02-20 00:36:43 +03:00
ctx . Redirect ( setting . AppSubURL + "/admin/monitor/queue/" + strconv . FormatInt ( qid , 10 ) )
2020-01-07 14:23:09 +03:00
return
}
if maxNumber < - 1 {
maxNumber = - 1
}
} else {
maxNumber = mq . MaxNumberOfWorkers ( )
}
if len ( numberStr ) > 0 {
number , err = strconv . Atoi ( numberStr )
if err != nil || number < 0 {
ctx . Flash . Error ( ctx . Tr ( "admin.monitor.queue.settings.numberworkers.error" ) )
2021-02-20 00:36:43 +03:00
ctx . Redirect ( setting . AppSubURL + "/admin/monitor/queue/" + strconv . FormatInt ( qid , 10 ) )
2020-01-07 14:23:09 +03:00
return
}
} else {
number = mq . BoostWorkers ( )
}
if len ( timeoutStr ) > 0 {
timeout , err = time . ParseDuration ( timeoutStr )
if err != nil {
ctx . Flash . Error ( ctx . Tr ( "admin.monitor.queue.settings.timeout.error" ) )
2021-02-20 00:36:43 +03:00
ctx . Redirect ( setting . AppSubURL + "/admin/monitor/queue/" + strconv . FormatInt ( qid , 10 ) )
2020-01-07 14:23:09 +03:00
return
}
} else {
2020-01-29 04:01:06 +03:00
timeout = mq . BoostTimeout ( )
2020-01-07 14:23:09 +03:00
}
2020-01-29 04:01:06 +03:00
mq . SetPoolSettings ( maxNumber , number , timeout )
2020-01-07 14:23:09 +03:00
ctx . Flash . Success ( ctx . Tr ( "admin.monitor.queue.settings.changed" ) )
2021-02-20 00:36:43 +03:00
ctx . Redirect ( setting . AppSubURL + "/admin/monitor/queue/" + strconv . FormatInt ( qid , 10 ) )
2020-01-07 14:23:09 +03:00
}