Add profiler, edit to database behaviour (Add retries)

dev
POUDEROUX Tom 2025-04-26 11:19:20 +02:00
parent 2e0ef0b7d8
commit b03e2d2af1
6 changed files with 341 additions and 33 deletions

1
.gitignore vendored
View File

@ -6,3 +6,4 @@ bin/
config.json
chromagies
log.txt
__debug*

View File

@ -37,7 +37,7 @@ func main() {
var conf config.Server = (config.GetConfig()).Server
http.ListenAndServe(fmt.Sprintf("%s:%s", conf.IP, conf.Port), router)
http.ListenAndServe(fmt.Sprintf("%s:%d", conf.IP, conf.Port), router)
stopServer()
}
@ -71,7 +71,7 @@ func initConfig() {
}
if err != nil {
log.Fatal("Cannot read/write config file !")
log.Fatalln("Cannot read/write config file !")
}
}

View File

@ -1,22 +1,32 @@
package api
import (
"chromagies/src/database"
"chromagies/src/profiler"
"fmt"
"net/http"
"time"
"github.com/gorilla/mux"
)
const (
profiler_API_PROCESS_REQUEST string = "API Process Request"
)
var apiRootPath string = "/api/v1"
func Init(router *mux.Router) {
profiler.Register(profiler_API_PROCESS_REQUEST, 10000, "µs")
router.HandleFunc(apiRootPath, apiRoot).Methods("GET")
routerAuth := router.PathPrefix(apiRootPath + "/auth").Subrouter()
routerPage := router.PathPrefix(apiRootPath + "/page").Subrouter()
routerUser := router.PathPrefix(apiRootPath + "/user").Subrouter()
routerTag := router.PathPrefix(apiRootPath + "/tag").Subrouter()
routerDebug := router.PathPrefix(apiRootPath + "/debug").Subrouter()
routerAuth.HandleFunc("", apiAuth)
routerAuth.HandleFunc("/login", apiAuthLogin)
@ -32,67 +42,118 @@ func Init(router *mux.Router) {
routerTag.HandleFunc("", apiTag)
routerTag.HandleFunc("/{name}", apiTagName)
routerDebug.HandleFunc("/profiler", apiDebugProfiler)
routerDebug.HandleFunc("/profiler/{name}", apiDebugProfilerName)
}
// API ROOT
func apiRoot(w http.ResponseWriter, r *http.Request) {
tStart := time.Now()
fmt.Fprintf(w, "API ROOT")
profiler.Add(profiler_API_PROCESS_REQUEST, time.Duration(time.Since(tStart).Microseconds()))
}
// API Auth
func apiAuth(w http.ResponseWriter, r *http.Request) {
tStart := time.Now()
fmt.Fprintf(w, "API Auth")
profiler.Add(profiler_API_PROCESS_REQUEST, time.Duration(time.Since(tStart).Microseconds()))
}
func apiAuthLogin(w http.ResponseWriter, r *http.Request) {
tStart := time.Now()
fmt.Fprintf(w, "API Auth Login")
database.Ping()
profiler.Add(profiler_API_PROCESS_REQUEST, time.Duration(time.Since(tStart).Microseconds()))
}
func apiAuthLogout(w http.ResponseWriter, r *http.Request) {
tStart := time.Now()
fmt.Fprintf(w, "API Auth Logout")
profiler.Add(profiler_API_PROCESS_REQUEST, time.Duration(time.Since(tStart).Microseconds()))
}
// API Page
func apiPage(w http.ResponseWriter, r *http.Request) {
tStart := time.Now()
fmt.Fprintf(w, "API Page")
profiler.Add(profiler_API_PROCESS_REQUEST, time.Duration(time.Since(tStart).Microseconds()))
}
func apiPageFolder(w http.ResponseWriter, r *http.Request) {
tStart := time.Now()
vars := mux.Vars(r)
fmt.Fprintf(w, "API Page Folder(%s)", vars["folder"])
profiler.Add(profiler_API_PROCESS_REQUEST, time.Duration(time.Since(tStart).Microseconds()))
}
func apiPageFolderPage(w http.ResponseWriter, r *http.Request) {
tStart := time.Now()
vars := mux.Vars(r)
fmt.Fprintf(w, "API Page Folder(%s) Page(%s)", vars["folder"], vars["page"])
profiler.Add(profiler_API_PROCESS_REQUEST, time.Duration(time.Since(tStart).Microseconds()))
}
func apiPageFolderPageContent(w http.ResponseWriter, r *http.Request) {
tStart := time.Now()
vars := mux.Vars(r)
fmt.Fprintf(w, "API Page Folder(%s) Page(%s) Content", vars["folder"], vars["page"])
profiler.Add(profiler_API_PROCESS_REQUEST, time.Duration(time.Since(tStart).Microseconds()))
}
// API User
func apiUser(w http.ResponseWriter, r *http.Request) {
tStart := time.Now()
fmt.Fprintf(w, "API User")
profiler.Add(profiler_API_PROCESS_REQUEST, time.Duration(time.Since(tStart).Microseconds()))
}
func apiUserName(w http.ResponseWriter, r *http.Request) {
tStart := time.Now()
vars := mux.Vars(r)
fmt.Fprintf(w, "API User Name(%s)", vars["name"])
profiler.Add(profiler_API_PROCESS_REQUEST, time.Duration(time.Since(tStart).Microseconds()))
}
// API Tag
func apiTag(w http.ResponseWriter, r *http.Request) {
tStart := time.Now()
fmt.Fprintf(w, "API Tag")
profiler.Add(profiler_API_PROCESS_REQUEST, time.Duration(time.Since(tStart).Microseconds()))
}
func apiTagName(w http.ResponseWriter, r *http.Request) {
tStart := time.Now()
vars := mux.Vars(r)
fmt.Fprintf(w, "API Tag Name(%s)", vars["name"])
profiler.Add(profiler_API_PROCESS_REQUEST, time.Duration(time.Since(tStart).Microseconds()))
}
// API Tag
func apiDebugProfiler(w http.ResponseWriter, r *http.Request) {
tStart := time.Now()
var entries []string
profiler.GetAll(&entries)
for i := range entries {
var entry string = entries[i]
fmt.Fprintf(w, "API Debug Profiler(%s)\n\n%s\n\n", entry, string(profiler.Get(entry).ToString()))
}
profiler.Add(profiler_API_PROCESS_REQUEST, time.Duration(time.Since(tStart).Microseconds()))
}
func apiDebugProfilerName(w http.ResponseWriter, r *http.Request) {
tStart := time.Now()
vars := mux.Vars(r)
fmt.Fprintf(w, "API Debug Profiler(%s)\n%s", vars["name"], string(profiler.Get(vars["name"]).ToString()))
profiler.Add(profiler_API_PROCESS_REQUEST, time.Duration(time.Since(tStart).Microseconds()))
}

View File

@ -10,10 +10,12 @@ const CONFIG_FILE_NAME string = "./config.json"
const CONFIG_FILE_DEFAULT_CONF string = `{
"Database": {
"Host": "localhost",
"Port": "3306",
"Port": 3306,
"Database": "Chromagies",
"User": "chromagies_user",
"Password": "12345678"
"Password": "12345678",
"RetriesOnError": 5,
"TimeBetweenRetriesMs": 100
},
"Logger": {
@ -21,16 +23,18 @@ const CONFIG_FILE_DEFAULT_CONF string = `{
},
"Server": {
"IP": "0.0.0.0",
"Port": "12345"
"Port": 12345
}
}`
type Database struct {
Host string
Port string
Database string
User string
Password string
Host string
Port uint16
Database string
User string
Password string
RetriesOnError uint16
TimeBetweenRetriesMs uint16
}
type Logger struct {
@ -39,7 +43,7 @@ type Logger struct {
type Server struct {
IP string
Port string
Port uint16
}
type Config struct {

View File

@ -2,53 +2,156 @@ package database
import (
"chromagies/src/config"
"chromagies/src/profiler"
"database/sql"
"fmt"
"log"
"time"
_ "github.com/go-sql-driver/mysql"
)
const (
profiler_DATABASE_CONNECT string = "Database Connect"
profiler_DATABASE_QUERY string = "Database Query"
profiler_DATABASE_PING string = "Database Ping"
)
var db *sql.DB
var dbConfig config.Database
var dbPath string
var isInit bool
func Init() {
var err error
if isInit {
return
}
dbConfig = config.GetConfig().Database
dbPath = fmt.Sprintf("%s:%s@(%s:%s)/%s?parseTime=true", dbConfig.User, dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database)
db, err = sql.Open("mysql", dbPath)
if err != nil {
log.Println("Cannot connect to database !")
log.Fatal(err)
}
profiler.Register(profiler_DATABASE_CONNECT, 20, "µs")
profiler.Register(profiler_DATABASE_QUERY, 4000, "µs")
profiler.Register(profiler_DATABASE_PING, 20, "µs")
err = db.Ping()
if err != nil {
log.Println("Cannot ping the database !")
log.Fatal(err)
}
isInit = true
Connect()
var query string = `SHOW GRANTS;`
var rows *sql.Rows = executeQuery(query)
defer rows.Close()
if rows != nil {
defer rows.Close()
for rows.Next() {
var res string
rows.Scan(&res)
fmt.Println(res)
}
}
for rows.Next() {
var res string
rows.Scan(&res)
fmt.Println(res)
}
func Terminate() {
isInit = false
if db != nil {
db.Close()
db = nil
}
}
func Connect() {
if !isInit {
return
}
if db != nil {
db.Close()
}
var err error
var dbPath string = fmt.Sprintf("%s:%s@(%s:%d)/%s?parseTime=true", dbConfig.User, dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database)
log.Printf("Connecting to database %s at %s:%d as %s\n", dbConfig.Database, dbConfig.Host, dbConfig.Port, dbConfig.User)
tStart := time.Now()
for i := range dbConfig.RetriesOnError {
db, err = sql.Open("mysql", dbPath)
if err == nil {
break
}
log.Printf("Error while connecting the database [%d/%d] : %s\n", i+1, dbConfig.RetriesOnError, err)
time.Sleep((time.Duration)(dbConfig.TimeBetweenRetriesMs) * time.Millisecond)
}
profiler.Add(profiler_DATABASE_CONNECT, time.Duration(time.Since(tStart).Microseconds()))
if err != nil {
log.Fatalln("Cannot connect to database !")
}
Ping()
}
func Disconnect() {
if !isInit {
return
}
if db == nil {
return
}
db.Close()
}
func Ping() {
if !isInit {
return
}
if db == nil {
return
}
var err error
tStart := time.Now()
for i := range dbConfig.RetriesOnError {
err = db.Ping()
if err == nil {
break
}
log.Printf("Error while pinging the database [%d/%d] : %s\n", i+1, dbConfig.RetriesOnError, err)
time.Sleep((time.Duration)(dbConfig.TimeBetweenRetriesMs) * time.Millisecond)
}
profiler.Add(profiler_DATABASE_PING, time.Duration(time.Since(tStart).Microseconds()))
if err != nil {
log.Fatalln("Cannot ping the database !")
}
}
func executeQuery(query string) *sql.Rows {
if !isInit {
return nil
}
if db == nil {
return nil
}
Ping()
var err error
var rows *sql.Rows
tStart := time.Now()
for i := range dbConfig.RetriesOnError {
rows, err = db.Query(query)
if err == nil {
break
}
log.Printf("Error while querying the database [%d/%d] : %s\n", i+1, dbConfig.RetriesOnError, err)
time.Sleep((time.Duration)(dbConfig.TimeBetweenRetriesMs) * time.Millisecond)
}
profiler.Add(profiler_DATABASE_QUERY, time.Duration(time.Since(tStart).Microseconds()))
rows, err = db.Query(query)
if err != nil {
log.Println("Error while query DB :")
log.Fatal(err)
log.Fatalln("Cannot query the database !")
}
return rows

View File

@ -0,0 +1,139 @@
package profiler
import (
"fmt"
"log"
"maps"
"math"
"slices"
"sync"
"time"
)
const PROFILER_DEFAULT_MAX_COUNT uint32 = 1000
const PROFILER_DEFAULT_UNIT string = ""
type Data struct {
count uint32
mean float64
min time.Duration
max time.Duration
lastValue time.Duration
unit string
}
type Profile struct {
sync.RWMutex
unit string
count uint32
maxCount uint32
index uint32
sum time.Duration
mean float64
values []time.Duration
lastValue time.Duration
}
var profiles map[string]*Profile = make(map[string]*Profile)
func Register(entry string, maxValues uint32, unit string) {
var profile *Profile
var exist bool
profile, exist = profiles[entry]
if exist && (profile != nil) {
return
}
profile = new(Profile)
profile.maxCount = maxValues
profile.values = make([]time.Duration, profile.maxCount)
profile.unit = unit
profiles[entry] = profile
}
func Add(entry string, duration time.Duration) {
var profile *Profile
var exist bool
profile, exist = profiles[entry]
if !exist || (profile == nil) {
profile = new(Profile)
profile.maxCount = PROFILER_DEFAULT_MAX_COUNT
profile.values = make([]time.Duration, profile.maxCount)
profile.unit = PROFILER_DEFAULT_UNIT
profiles[entry] = profile
}
profile.Lock()
var lastValue time.Duration = profile.values[profile.index]
profile.values[profile.index] = duration
profile.index++
if profile.index >= profile.maxCount {
profile.index = 0
}
if profile.count < profile.maxCount {
profile.count++
}
profile.sum += duration - lastValue
profile.mean = (((float64)(profile.sum)) / (float64)(time.Duration(profile.count)))
profile.lastValue = duration
profile.Unlock()
}
func GetAll(entries *[]string) {
*entries = slices.Collect(maps.Keys(profiles))
}
func Get(entry string) Data {
var profile *Profile
var exist bool
profile, exist = profiles[entry]
if !exist || (profile == nil) {
profile = new(Profile)
profiles[entry] = profile
}
var data Data
profile.RLock()
data.mean = profile.mean
data.min = math.MaxInt64
data.max = 0
data.count = profile.count
data.unit = profile.unit
data.lastValue = profile.lastValue
for i := range profile.count {
value := profile.values[i]
if value < data.min {
data.min = value
}
if value > data.max {
data.max = value
}
}
profile.RUnlock()
return data
}
func (data Data) ToString() string {
return fmt.Sprintf(`
- Last value : %d %s
- Mean : %f %s
- Min : %d %s
- Max : %d %s
- Number of values : %d
`, data.lastValue, data.unit, data.mean, data.unit, data.min, data.unit, data.max, data.unit, data.count)
}
func Log(entry string) {
log.Println(Get(entry).ToString())
}