Generates random data for development or performance testing.
Signed-off-by: Franciszek Stachura <[email protected]>
---
For some reason, keeping a long-running connection with large bulk
inserts causes Postgres to use a lot of memory, until the connection is
closed. The only solution I found is making the connections
short-lived with SetConnMaxLifetime
(I believe that it should be used anyway).
I don't see any good way of manually closing connections in Bun.
cmd/pw/db/generate.go | 285 ++++++++++++++++++++++++++++++++++++++++++
cmd/pw/db/main.go | 9 +-
2 files changed, 290 insertions(+), 4 deletions(-)
create mode 100644 cmd/pw/db/generate.go
diff --git a/cmd/pw/db/generate.go b/cmd/pw/db/generate.go
new file mode 100644
index 0000000..ca1f18e
--- /dev/null
+++ b/cmd/pw/db/generate.go
@@ -0,0 +1,285 @@
+// Patchwork - automated patch tracking system
+// Copyright (C) The Patchwork Contributors (see CONTRIBUTORS)
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package db
+
+import (
+ "context"
+ "fmt"
+ "math/rand"
+ "time"
+
+ "github.com/getpatchwork/patchwork/cmd/pw/pw"
+ "github.com/getpatchwork/patchwork/pkg/db"
+ "github.com/getpatchwork/patchwork/pkg/events"
+)
+
+type GenerateCmd struct {
+ Name string `required:"" help:"Name of the project."`
+ Series int `required:"" help:"Number of series to generate. Each
series will have up to 20 patches."`
+ Authors int `required:"" help:"Number of authors to generate."`
+}
+
+const BATCH_SIZE = 1000
+
+func (c *GenerateCmd) Run(ctx context.Context) error {
+ database := pw.GetDB(ctx)
+
+ bus := events.Start(ctx, database)
+ defer bus.Shutdown()
+ ctx = db.WithBus(ctx, bus)
+
+ projectName := c.Name
+
+ project := db.Project{
+ Name: projectName,
+ Linkname: projectName,
+ Listid: projectName,
+ Listemail: fmt.Sprintf("%s@%s.example.com",
projectName, projectName),
+ WebURL: "",
+ ScmURL: "",
+ WebScmURL: "",
+ ListArchiveURL: "",
+ SubjectMatch: "",
+ CommitURLFormat: "",
+ ListArchiveURLFormat: "",
+ }
+ _, err := database.NewInsert().Model(&project).Exec(ctx)
+ if err != nil {
+ return err
+ }
+
+ fmt.Printf("Created project %q (id=%d)\n", project.Name, project.ID)
+
+ var states []db.State
+ err = database.NewSelect().Model(&states).Scan(ctx)
+ if err != nil {
+ return err
+ }
+
+ var tags []db.Tag
+ err = database.NewSelect().Model(&tags).Scan(ctx)
+ if err != nil {
+ return err
+ }
+
+ var authors []db.Person
+ for i := range c.Authors {
+ personName := fmt.Sprintf("person_%s_%d", projectName, i)
+
+ person := db.Person{
+ Email: fmt.Sprintf("%s@%s.example.com", personName,
projectName),
+ Name: &personName,
+ }
+ _, err := database.NewInsert().Model(&person).Exec(ctx)
+ if err != nil {
+ return err
+ }
+
+ authors = append(authors, person)
+ }
+
+ series_batches := c.Series / BATCH_SIZE
+ last_batch_size := c.Series % BATCH_SIZE
+ if last_batch_size != 0 {
+ series_batches += 1
+ }
+
+ for sb := range series_batches {
+ var batch_size int
+ if sb == series_batches-1 && last_batch_size != 0 {
+ batch_size = last_batch_size
+ } else {
+ batch_size = BATCH_SIZE
+ }
+
+ // Create covers
+
+ var batch_covers []db.Cover
+ for b := range batch_size {
+ batch_covers = append(
+ batch_covers,
+ randCover(project,
authors[rand.Int()%len(authors)], sb*BATCH_SIZE+b),
+ )
+ }
+
+ _, err :=
database.NewInsert().Model(&batch_covers).Returning("id").Exec(ctx)
+ if err != nil {
+ return err
+ }
+
+ // Create series
+
+ var batch_series []db.Series
+ for _, cover := range batch_covers {
+ batch_series = append(batch_series, randSeries(project,
cover))
+ }
+ _, err =
database.NewInsert().Model(&batch_series).Returning("id").Exec(ctx)
+ if err != nil {
+ return err
+ }
+
+ // Create patches for each series
+
+ var batch_patches []db.Patch
+ for s, series := range batch_series {
+ for p := range series.Total {
+ name := fmt.Sprintf("series %d patch %d",
sb*BATCH_SIZE+s, p)
+ batch_patches = append(
+ batch_patches,
+ randPatch(project, series,
states[rand.Int()%len(states)], name, p),
+ )
+ }
+ }
+
+ err =
database.NewInsert().Model(&batch_patches).Returning("id").Scan(ctx)
+ if err != nil {
+ return err
+ }
+
+ // Create tags and checks
+
+ var batch_tags []db.PatchTag
+ var batch_checks []db.Check
+ for _, patch := range batch_patches {
+ for t := range len(tags) {
+ if rand.Int()%(t+2) != t {
+ continue
+ }
+
+ batch_tags = append(batch_tags, db.PatchTag{
+ PatchID: patch.ID,
+ TagID: tags[t].ID,
+ Count: rand.Int() % 10,
+ })
+ }
+
+ for range rand.Int() % 10 {
+ batch_checks = append(batch_checks,
randCheck(patch))
+ }
+ }
+
+ err =
database.NewInsert().Model(&batch_tags).Returning("").Scan(ctx)
+ if err != nil {
+ return err
+ }
+
+ err =
database.NewInsert().Model(&batch_checks).Returning("").Scan(ctx)
+ if err != nil {
+ return err
+ }
+
+ fmt.Printf("\rgenerated series %d/%d",
sb*BATCH_SIZE+batch_size, c.Series)
+ }
+ fmt.Printf("\n")
+
+ return nil
+}
+
+func randString(len uint32) string {
+ res := make([]byte, len)
+ for i := range len {
+ t := rand.Uint32() % 3
+ if t == 0 {
+ // 0-9
+ res[i] = byte(48 + (rand.Uint32() % 10))
+ } else if t == 1 {
+ // A-Z
+ res[i] = byte(65 + (rand.Uint32() % 25))
+ } else {
+ // a-z
+ res[i] = byte(97 + (rand.Uint32() % 25))
+ }
+ }
+
+ return string(res)
+}
+
+func randTextBlock(lines uint32, lineLen uint32) string {
+ result := make([]byte, (lineLen+1)*lines)
+ for i := range lines {
+ line := randString(lineLen)
+ copy(result[i*lineLen+1:], []byte(line))
+ result[(i+1)*lineLen] = '\n'
+ }
+ return string(result)
+}
+
+func randColor() int {
+ r := (rand.Int() % 200) + 40
+ g := (rand.Int() % 200) + 40
+ b := (rand.Int() % 200) + 40
+
+ return (r << 16) | (g << 8) | b
+}
+
+func randCover(project db.Project, submitter db.Person, num int) db.Cover {
+ name := fmt.Sprintf("cover %d", num)
+ content := randTextBlock((rand.Uint32()%500)+10, 72)
+ headers := randTextBlock((rand.Uint32()%40)+10, 72)
+ msgid := fmt.Sprintf("<%s@%s.example.com>", randString(15),
project.Name)
+
+ return db.Cover{
+ Msgid: msgid,
+ Date: time.Now(),
+ Headers: headers,
+ SubmitterID: submitter.ID,
+ Content: &content,
+ ProjectID: project.ID,
+ Name: name,
+ }
+}
+
+func randSeries(project db.Project, cover db.Cover) db.Series {
+ patches := rand.Int() % 20
+
+ return db.Series{
+ ProjectID: &project.ID,
+ CoverLetterID: &cover.ID,
+ Name: &cover.Name,
+ Date: time.Now(),
+ SubmitterID: cover.SubmitterID,
+ Version: 0,
+ Total: patches,
+
+ ReceivedTotal: patches,
+ ReceivedAll: true,
+ }
+}
+
+func randPatch(project db.Project, series db.Series, state db.State, name
string, num int) db.Patch {
+ msgid := fmt.Sprintf("<%s@%s.example.com>", randString(15),
project.Name)
+ content := randTextBlock((rand.Uint32()%500)+10, 72)
+ headers := randTextBlock((rand.Uint32()%40)+10, 72)
+ diff := ""
+ hash := randString(32)
+
+ return db.Patch{
+ Msgid: msgid,
+ Date: time.Now(),
+ Headers: headers,
+ SubmitterID: series.SubmitterID,
+ Content: &content,
+ ProjectID: project.ID,
+ Name: name,
+ Diff: &diff,
+ StateID: &state.ID,
+ Archived: (rand.Int() % 2) == 0,
+ Hash: &hash,
+ SeriesID: &series.ID,
+ Number: &num,
+ }
+}
+
+func randCheck(patch db.Patch) db.Check {
+ return db.Check{
+ PatchID: patch.ID,
+ Date: time.Now(),
+ State: db.CheckState(rand.Int() % 4),
+ TargetURL: "",
+ Context: fmt.Sprintf("check_%d", rand.Int()%5),
+ Description: "",
+ }
+}
diff --git a/cmd/pw/db/main.go b/cmd/pw/db/main.go
index 6c12f88..8fed09c 100644
--- a/cmd/pw/db/main.go
+++ b/cmd/pw/db/main.go
@@ -7,8 +7,9 @@ package db
// CLI groups the database management subcommands.
type CLI struct {
- Sync SyncCmd `cmd:"" help:"Create or update the database schema."`
- Status StatusCmd `cmd:"" help:"Show database schema migrations status."`
- Export ExportCmd `cmd:"" help:"Export database contents as SQL."`
- Import ImportCmd `cmd:"" name:"import" help:"Import SQL data from
stdin."`
+ Sync SyncCmd `cmd:"" help:"Create or update the database
schema."`
+ Status StatusCmd `cmd:"" help:"Show database schema migrations
status."`
+ Export ExportCmd `cmd:"" help:"Export database contents as SQL."`
+ Import ImportCmd `cmd:"" name:"import" help:"Import SQL data from
stdin."`
+ Generate GenerateCmd `cmd:"" name:"generate" help:"Generate random
data."`
}
--
2.55.0
_______________________________________________
Patchwork mailing list
[email protected]
https://lists.ozlabs.org/listinfo/patchwork