2023-06-23 09:36:08 +02:00
|
|
|
/*
|
|
|
|
|
Copyright © 2023 Thomas von Dein
|
|
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
|
|
Commentary:
|
|
|
|
|
|
|
|
|
|
This little program implements:
|
|
|
|
|
https://aws.amazon.com/blogs/database/performance-impact-of-idle-postgresql-connections/
|
|
|
|
|
|
|
|
|
|
It opens 1k concurrent postgres connections, does a couple of
|
|
|
|
|
selects and then stops doing anything further, thus creating
|
|
|
|
|
hanging idle sessions.
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"database/sql"
|
|
|
|
|
"fmt"
|
|
|
|
|
"log"
|
2025-11-05 19:55:35 +01:00
|
|
|
"os"
|
2023-06-23 09:36:08 +02:00
|
|
|
"sync"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
_ "github.com/lib/pq"
|
|
|
|
|
flag "github.com/spf13/pflag"
|
|
|
|
|
)
|
|
|
|
|
|
2025-11-05 19:55:35 +01:00
|
|
|
const version = "v0.0.2"
|
|
|
|
|
|
2023-06-23 09:36:08 +02:00
|
|
|
type Tableschema struct {
|
|
|
|
|
Name string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Maxloop int = 100
|
|
|
|
|
const Getschema string = "SELECT table_schema||'.'||table_name as relname from information_schema.tables WHERE table_schema='information_schema';"
|
|
|
|
|
const Gettable string = "SELECT * FROM information_schema.columns LIMIT 1;"
|
|
|
|
|
const StartTransaction string = "BEGIN TRANSACTION"
|
|
|
|
|
const HostConnection string = "user=%s dbname=%s password=%s"
|
|
|
|
|
const NetConnection string = "user=%s dbname=%s password=%s host=%s port=%d"
|
|
|
|
|
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
var optPasswd string
|
|
|
|
|
var optUser string
|
|
|
|
|
var optDatabase string
|
|
|
|
|
var optServer string
|
|
|
|
|
var optPort int
|
|
|
|
|
var optMaxconnections int
|
|
|
|
|
var optTimeout int
|
2025-11-05 19:55:35 +01:00
|
|
|
var optIdleTransaction, shversion bool
|
2023-06-23 09:36:08 +02:00
|
|
|
var ctx context.Context
|
2025-11-05 19:50:20 +01:00
|
|
|
var cancel context.CancelFunc
|
2023-06-23 09:36:08 +02:00
|
|
|
var conn string
|
|
|
|
|
|
|
|
|
|
flag.StringVarP(&optPasswd, "password", "p", "", "Password of the database user")
|
|
|
|
|
flag.StringVarP(&optUser, "user", "u", "postgres", "Database user")
|
|
|
|
|
flag.StringVarP(&optDatabase, "database", "d", "postgres", "Database")
|
|
|
|
|
flag.StringVarP(&optServer, "server", "s", "localhost", "Server")
|
|
|
|
|
flag.IntVarP(&optMaxconnections, "client", "c", 500, "Number of concurrent users")
|
|
|
|
|
flag.IntVarP(&optPort, "port", "P", 5432, "TCP Port")
|
2025-11-05 19:55:35 +01:00
|
|
|
flag.IntVarP(&optTimeout, "timeout", "t", 0, "Whether to stop the clients after N seconds")
|
2023-06-23 09:36:08 +02:00
|
|
|
flag.BoolVarP(&optIdleTransaction, "idletransaction", "i", false, "Wether to stay in idle in transaction state")
|
2025-11-05 19:55:35 +01:00
|
|
|
flag.BoolVarP(&shversion, "version", "v", false, "show version")
|
2023-06-23 09:36:08 +02:00
|
|
|
|
|
|
|
|
flag.Parse()
|
|
|
|
|
|
2025-11-05 19:55:35 +01:00
|
|
|
if shversion {
|
|
|
|
|
fmt.Printf("pgidler version %s\n", version)
|
|
|
|
|
os.Exit(0)
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-23 09:36:08 +02:00
|
|
|
if optServer != "" {
|
|
|
|
|
conn = fmt.Sprintf(NetConnection, optUser, optDatabase, optPasswd, optServer, optPort)
|
|
|
|
|
} else {
|
|
|
|
|
conn = fmt.Sprintf(HostConnection, optUser, optDatabase, optPasswd)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// first do a connection test
|
|
|
|
|
db, err := sql.Open("postgres", conn)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
}
|
2025-11-05 19:50:20 +01:00
|
|
|
|
|
|
|
|
if err := db.Close(); err != nil {
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
}
|
2023-06-23 09:36:08 +02:00
|
|
|
|
|
|
|
|
log.Printf("DB Connection works, firing up %d clients\n", optMaxconnections)
|
|
|
|
|
|
|
|
|
|
if optTimeout > 0 {
|
2025-11-05 19:50:20 +01:00
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), time.Duration(optTimeout)*time.Second)
|
2023-06-23 09:36:08 +02:00
|
|
|
log.Printf("Clients will be killed after %d seconds", optTimeout)
|
|
|
|
|
} else {
|
|
|
|
|
ctx = context.TODO()
|
|
|
|
|
log.Println("Clients will run endlessly, abort with C-c")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wg.Add(optMaxconnections)
|
|
|
|
|
|
|
|
|
|
for m := 0; m < optMaxconnections; m++ {
|
|
|
|
|
go dbClient(ctx, conn, optIdleTransaction)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wg.Wait()
|
2025-11-05 19:50:20 +01:00
|
|
|
|
|
|
|
|
if cancel != nil {
|
|
|
|
|
cancel()
|
|
|
|
|
}
|
2023-06-23 09:36:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func dbClient(ctx context.Context, conn string, idle bool) {
|
|
|
|
|
defer wg.Done()
|
|
|
|
|
|
|
|
|
|
db, err := sql.Open("postgres", conn)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if idle {
|
|
|
|
|
_, err := db.Exec(StartTransaction)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
rows, err := db.Query(Getschema)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
}
|
2025-11-05 19:50:20 +01:00
|
|
|
defer func() {
|
|
|
|
|
if err := rows.Close(); err != nil {
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
2023-06-23 09:36:08 +02:00
|
|
|
//log.Println("Got rows")
|
|
|
|
|
|
|
|
|
|
for rows.Next() {
|
|
|
|
|
var T Tableschema
|
|
|
|
|
if err := rows.Scan(&T.Name); err != nil {
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
}
|
2025-11-05 19:50:20 +01:00
|
|
|
|
2023-06-23 09:36:08 +02:00
|
|
|
//log.Printf("Got table %s\n", T.Name)
|
|
|
|
|
|
|
|
|
|
for i := 0; i < Maxloop; i++ {
|
|
|
|
|
rows, err := db.Query(Gettable)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
}
|
2025-11-05 19:50:20 +01:00
|
|
|
|
|
|
|
|
// ignore result
|
|
|
|
|
if err := rows.Close(); err != nil {
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
}
|
2023-06-23 09:36:08 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//log.Println("Got tables")
|
|
|
|
|
|
|
|
|
|
// block this thread forever or timeout
|
|
|
|
|
//select {}
|
|
|
|
|
select {
|
|
|
|
|
case <-time.After(1 * time.Second):
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
}
|
|
|
|
|
}
|