mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-08 00:22:14 +01:00
325 lines
7.2 KiB
Go
325 lines
7.2 KiB
Go
package process
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os/exec"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/thecodingmachine/gotenberg/internal/pkg/conf"
|
|
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
|
|
"github.com/thecodingmachine/gotenberg/internal/pkg/xexec"
|
|
"github.com/thecodingmachine/gotenberg/internal/pkg/xlog"
|
|
"github.com/thecodingmachine/gotenberg/internal/pkg/xtime"
|
|
)
|
|
|
|
type jlistItem struct {
|
|
Name string `json:"name"`
|
|
PM2Env struct {
|
|
Status string `json:"status"`
|
|
RestartTime int64 `json:"restart_time"`
|
|
} `json:"pm2_env"`
|
|
Monit struct {
|
|
Memory int64 `json:"memory"`
|
|
CPU float64 `json:"cpu"`
|
|
} `json:"monit"`
|
|
}
|
|
|
|
type jlist []jlistItem
|
|
|
|
func (list jlist) toList() List {
|
|
var result List
|
|
for _, current := range list {
|
|
item := ListItem{
|
|
Name: current.Name,
|
|
Status: current.PM2Env.Status,
|
|
Restart: current.PM2Env.RestartTime,
|
|
Memory: current.Monit.Memory, // TODO humanize?
|
|
CPU: current.Monit.CPU,
|
|
}
|
|
result = append(result, item)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (list jlist) isOnline(p Process) bool {
|
|
const onlineStatus string = "online"
|
|
for _, item := range list {
|
|
if item.Name == p.ID() {
|
|
return item.PM2Env.Status == onlineStatus
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (list jlist) memory(p Process) (int64, error) {
|
|
const op string = "process.jlist.memory"
|
|
for _, item := range list {
|
|
if item.Name == p.ID() {
|
|
return item.Monit.Memory, nil
|
|
}
|
|
}
|
|
return 0, xerror.New(
|
|
op,
|
|
fmt.Errorf("'%s' does not exist in the list of PM2 processes", p.ID()),
|
|
)
|
|
}
|
|
|
|
type command string
|
|
|
|
const (
|
|
startCommand command = "start"
|
|
restartCommand command = "restart"
|
|
stopCommand command = "stop"
|
|
logsCommand command = "logs"
|
|
jlistCommand command = "jlist"
|
|
)
|
|
|
|
const maximumRestartAttempts uint = 3
|
|
|
|
type pm2Manager struct {
|
|
logger xlog.Logger
|
|
config conf.Config
|
|
pool map[Key][]Process
|
|
list *jlist
|
|
listLock *sync.Mutex
|
|
}
|
|
|
|
// NewPM2Manager returns a PM2 manager.
|
|
func NewPM2Manager(logger xlog.Logger, config conf.Config) Manager {
|
|
const op string = "process.NewPM2Manager"
|
|
m := &pm2Manager{
|
|
logger: logger,
|
|
config: config,
|
|
pool: make(map[Key][]Process),
|
|
listLock: &sync.Mutex{},
|
|
}
|
|
if !config.DisableGoogleChrome() {
|
|
processes := make([]Process, 2)
|
|
availablePort := 9222
|
|
// TODO from config
|
|
for i := 0; i < 2; i++ {
|
|
proc := chromeProcess{
|
|
host: "127.0.0.1",
|
|
port: availablePort,
|
|
}
|
|
proc.id = fmt.Sprintf("%s-%d", proc.binary(), proc.port)
|
|
processes[i] = proc
|
|
logger.DebugfOp(op, "added new process %v", proc)
|
|
availablePort++
|
|
}
|
|
m.pool[ChromeKey] = processes
|
|
}
|
|
if !config.DisableUnoconv() {
|
|
processes := make([]Process, 2)
|
|
availablePort := 2002
|
|
// TODO from config
|
|
for i := 0; i < 2; i++ {
|
|
proc := sofficeProcess{
|
|
host: "127.0.0.1",
|
|
port: availablePort,
|
|
}
|
|
proc.id = fmt.Sprintf("%s-%d", proc.binary(), proc.port)
|
|
processes[i] = proc
|
|
logger.DebugfOp(op, "added new process %v", proc)
|
|
availablePort++
|
|
}
|
|
m.pool[SofficeKey] = processes
|
|
}
|
|
// update the manager processes list
|
|
// only if there are processes.
|
|
if !config.DisableGoogleChrome() || !config.DisableUnoconv() {
|
|
go m.jlistTimer()
|
|
}
|
|
return m
|
|
}
|
|
|
|
func (m *pm2Manager) jlistTimer() {
|
|
const op string = "process.pm2Manager.jlistTimer"
|
|
duration := xtime.Duration(10)
|
|
resolver := func() error {
|
|
m.listLock.Lock()
|
|
defer m.listLock.Unlock()
|
|
out, err := exec.
|
|
Command("pm2", string(jlistCommand)).
|
|
Output()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
data := &jlist{}
|
|
if err := json.Unmarshal(out, data); err != nil {
|
|
return err
|
|
}
|
|
m.list = data
|
|
return nil
|
|
}
|
|
// update every x seconds the
|
|
// list from the manager.
|
|
for range time.Tick(duration) {
|
|
if err := resolver(); err != nil {
|
|
m.logger.ErrorOp(op, xerror.New(op, err))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (m *pm2Manager) Start() error {
|
|
const op string = "process.pm2Manager.Start"
|
|
for _, processes := range m.pool {
|
|
for _, proc := range processes {
|
|
if err := m.start(proc); err != nil {
|
|
return xerror.New(op, err)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *pm2Manager) start(p Process) error {
|
|
const op string = "process.pm2Manager.start"
|
|
resolver := func() error {
|
|
// first, we try to start the process.
|
|
if err := m.run(startCommand, p); err != nil {
|
|
return err
|
|
}
|
|
// we wait the process to be ready.
|
|
m.warmup(p)
|
|
// if the process failed to start correctly,
|
|
// we have to restart it.
|
|
if !m.IsViable(p) && maximumRestartAttempts > 0 {
|
|
return m.Restart(p)
|
|
}
|
|
// the process is viable, let's log its
|
|
// output.
|
|
return m.logs(p)
|
|
}
|
|
if err := resolver(); err != nil {
|
|
return xerror.New(op, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *pm2Manager) List() List {
|
|
m.listLock.Lock()
|
|
defer m.listLock.Unlock()
|
|
return m.list.toList()
|
|
}
|
|
|
|
func (m *pm2Manager) All() []Process {
|
|
var result []Process
|
|
for _, processes := range m.pool {
|
|
result = append(result, processes...)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (m *pm2Manager) Processes(key Key) []Process {
|
|
return m.pool[key]
|
|
}
|
|
|
|
func (m *pm2Manager) Restart(p Process) error {
|
|
const op string = "process.pm2Manager.Restart"
|
|
resolver := func() error {
|
|
var attempts uint
|
|
for attempts < maximumRestartAttempts {
|
|
// we restart the process.
|
|
if err := m.run(restartCommand, p); err != nil {
|
|
return err
|
|
}
|
|
// we wait the process to be ready.
|
|
m.warmup(p)
|
|
attempts++
|
|
// if the process is viable, we
|
|
// leave.
|
|
if m.IsViable(p) {
|
|
return m.logs(p)
|
|
}
|
|
}
|
|
return fmt.Errorf("failed to start '%s'", p.ID())
|
|
}
|
|
if err := resolver(); err != nil {
|
|
return xerror.New(op, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *pm2Manager) Stop(p Process) error {
|
|
const op string = "process.pm2Manager.Stop"
|
|
if err := m.run(stopCommand, p); err != nil {
|
|
return xerror.New(op, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *pm2Manager) IsViable(p Process) bool {
|
|
if !p.viabilityFunc()(m.logger) {
|
|
return false
|
|
}
|
|
m.listLock.Lock()
|
|
defer m.listLock.Unlock()
|
|
return m.list.isOnline(p)
|
|
}
|
|
|
|
func (m *pm2Manager) Memory(p Process) (int64, error) {
|
|
const op string = "process.pm2Manager.Memory"
|
|
m.listLock.Lock()
|
|
defer m.listLock.Unlock()
|
|
memory, err := m.list.memory(p)
|
|
if err != nil {
|
|
return 0, xerror.New(op, err)
|
|
}
|
|
return memory, nil
|
|
}
|
|
|
|
func (m *pm2Manager) warmup(p Process) {
|
|
const op string = "process.pm2Manager.warmup"
|
|
warmupTime := p.warmupTime()
|
|
m.logger.DebugfOp(
|
|
op,
|
|
"waiting '%v' for allowing '%s' to warmup",
|
|
warmupTime,
|
|
p.ID(),
|
|
)
|
|
time.Sleep(warmupTime)
|
|
}
|
|
|
|
func (m *pm2Manager) logs(p Process) error {
|
|
const op string = "process.pm2Manager.logs"
|
|
if m.config.LogLevel() == xlog.DebugLevel {
|
|
if err := m.run(logsCommand, p); err != nil {
|
|
return xerror.New(op, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *pm2Manager) run(pm2Cmd command, p Process) error {
|
|
const op string = "process.pm2Manager.run"
|
|
resolver := func() error {
|
|
args := []string{
|
|
string(pm2Cmd),
|
|
p.binary(),
|
|
}
|
|
if pm2Cmd == startCommand {
|
|
args = append(args, fmt.Sprintf("--name=%s", p.ID()))
|
|
args = append(args, "--interpreter=none", "--")
|
|
args = append(args, p.args()...)
|
|
}
|
|
cmd, err := xexec.Command(m.logger, "pm2", args...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
xexec.LogBeforeExecute(m.logger, cmd)
|
|
return cmd.Start()
|
|
}
|
|
if err := resolver(); err != nil {
|
|
return xerror.New(op, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Compile-time checks to ensure type implements desired interfaces.
|
|
var (
|
|
_ = Manager(new(pm2Manager))
|
|
)
|