-
Notifications
You must be signed in to change notification settings - Fork 16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
performance monitor package #2046
Changes from 5 commits
1e99a71
7bd548d
51fc43d
1483702
91ce438
9369efe
21b359f
a79f0c7
fef5daf
b17cb6d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# Perf | ||
|
||
Perf is a performance test module that is scheduled to run and cache those tests results in redis which can be retrieved later over RMB call. | ||
|
||
Perf tests are monitored by `noded` service from zos modules. | ||
|
||
### Usage Example | ||
|
||
1. Create a task that implement `Task` interface | ||
|
||
```go | ||
type LoggingTask struct { | ||
TaskID string | ||
Schedule string // should be in cron syntax with seconds ("* 0 * * * *") | ||
} | ||
|
||
func (t *LoggingTask) ID() string { | ||
return t.TaskID | ||
} | ||
|
||
func (t *LoggingTask) Cron() string { | ||
return t.Schedule | ||
} | ||
|
||
// a simple task that returns a string with the current time | ||
func (t *LoggingTask) Run(ctx context.Context) (interface{}, error) { | ||
result := fmt.Sprintf("time is: %v", time.Now()) | ||
return result, nil | ||
} | ||
``` | ||
|
||
2. Add the created task to scheduler | ||
|
||
```go | ||
perfMon.AddTask(&perf.LoggingTask{ | ||
TaskID: "LoggingTask", | ||
Schedule: "* 0 * * * *", // when minutes is 0 (aka: every hour) | ||
}) | ||
``` |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,103 @@ | ||||||
package perf | ||||||
|
||||||
import ( | ||||||
"context" | ||||||
"encoding/json" | ||||||
"fmt" | ||||||
|
||||||
"github.com/garyburd/redigo/redis" | ||||||
"github.com/pkg/errors" | ||||||
) | ||||||
|
||||||
const ( | ||||||
moduleName = "perf" | ||||||
) | ||||||
|
||||||
// TaskResult the result test schema | ||||||
type TaskResult struct { | ||||||
TaskName string `json:"task_name"` | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The type is already name TaskResult, so I can perfectly call this
Suggested change
|
||||||
Timestamp uint64 `json:"timestamp"` | ||||||
Result interface{} `json:"result"` | ||||||
} | ||||||
|
||||||
func generateKey(taskName string) string { | ||||||
return fmt.Sprintf("%s.%s", moduleName, taskName) | ||||||
} | ||||||
|
||||||
// setCache set result in redis | ||||||
func (pm *PerformanceMonitor) setCache(ctx context.Context, result TaskResult) error { | ||||||
data, err := json.Marshal(result) | ||||||
if err != nil { | ||||||
return errors.Wrap(err, "failed to marshal data to JSON") | ||||||
} | ||||||
|
||||||
conn := pm.pool.Get() | ||||||
defer conn.Close() | ||||||
|
||||||
_, err = conn.Do("SET", generateKey(result.TaskName), data) | ||||||
return err | ||||||
} | ||||||
|
||||||
// Get gets data from redis | ||||||
func (pm *PerformanceMonitor) Get(taskName string) (TaskResult, error) { | ||||||
var res TaskResult | ||||||
|
||||||
conn := pm.pool.Get() | ||||||
defer conn.Close() | ||||||
|
||||||
data, err := conn.Do("GET", generateKey(taskName)) | ||||||
if err != nil { | ||||||
return res, errors.Wrap(err, "failed to get the result") | ||||||
} | ||||||
|
||||||
err = json.Unmarshal(data.([]byte), &res) | ||||||
if err != nil { | ||||||
return res, errors.Wrap(err, "failed to unmarshal data from json") | ||||||
} | ||||||
|
||||||
return res, nil | ||||||
} | ||||||
|
||||||
func (pm *PerformanceMonitor) GetAll() ([]TaskResult, error) { | ||||||
var res []TaskResult | ||||||
|
||||||
conn := pm.pool.Get() | ||||||
defer conn.Close() | ||||||
|
||||||
var keys []string | ||||||
|
||||||
cursor := 0 | ||||||
for { | ||||||
values, err := redis.Values(conn.Do("SCAN", cursor, "MATCH", generateKey("*"))) | ||||||
if err != nil { | ||||||
return nil, err | ||||||
} | ||||||
|
||||||
_, err = redis.Scan(values, &cursor, &keys) | ||||||
if err != nil { | ||||||
return nil, err | ||||||
} | ||||||
|
||||||
for _, key := range keys { | ||||||
value, err := conn.Do("GET", key) | ||||||
if err != nil { | ||||||
continue | ||||||
} | ||||||
|
||||||
var result TaskResult | ||||||
|
||||||
err = json.Unmarshal(value.([]byte), &result) | ||||||
if err != nil { | ||||||
return res, errors.Wrap(err, "failed to unmarshal data from json") | ||||||
} | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not just call |
||||||
|
||||||
res = append(res, result) | ||||||
} | ||||||
|
||||||
if cursor == 0 { | ||||||
break | ||||||
} | ||||||
|
||||||
} | ||||||
return res, nil | ||||||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,69 @@ | ||||||
package perf | ||||||
|
||||||
import ( | ||||||
"context" | ||||||
"time" | ||||||
|
||||||
"github.com/go-co-op/gocron" | ||||||
"github.com/gomodule/redigo/redis" | ||||||
|
||||||
"github.com/pkg/errors" | ||||||
"github.com/threefoldtech/zos/pkg/utils" | ||||||
) | ||||||
|
||||||
// PerformanceMonitor holds the module data | ||||||
type PerformanceMonitor struct { | ||||||
scheduler *gocron.Scheduler | ||||||
pool redis.Pool | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
check the other comment to see why |
||||||
tasks []Task | ||||||
} | ||||||
|
||||||
// NewPerformanceMonitor returns PerformanceMonitor instance | ||||||
func NewPerformanceMonitor(redisAddr string) (*PerformanceMonitor, error) { | ||||||
redisPool, err := utils.NewRedisPool(redisAddr) | ||||||
if err != nil { | ||||||
return nil, errors.Wrap(err, "failed creating new redis pool") | ||||||
} | ||||||
|
||||||
scheduler := gocron.NewScheduler(time.UTC) | ||||||
|
||||||
return &PerformanceMonitor{ | ||||||
scheduler: scheduler, | ||||||
pool: *redisPool, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can't do that here, this fails already in CI. redisPool is already a pointer right? and that's is exactly what u need to keep on the PerfomanceMonitor structure There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reason this is done because when u do a It's not caught by the compiler but by the CI tests |
||||||
tasks: []Task{}, | ||||||
}, nil | ||||||
} | ||||||
|
||||||
// AddTask a simple helper method to add new tasks | ||||||
func (pm *PerformanceMonitor) AddTask(task Task) { | ||||||
pm.tasks = append(pm.tasks, task) | ||||||
} | ||||||
|
||||||
// Run adds the tasks to the corn queue and start the scheduler | ||||||
func (pm *PerformanceMonitor) Run(ctx context.Context) error { | ||||||
for _, task := range pm.tasks { | ||||||
_, err := pm.scheduler.CronWithSeconds(task.Cron()).Do(func() error { | ||||||
res, err := task.Run(ctx) | ||||||
if err != nil { | ||||||
return errors.Wrapf(err, "failed to run task: %s", task.ID()) | ||||||
} | ||||||
|
||||||
err = pm.setCache(ctx, TaskResult{ | ||||||
TaskName: task.ID(), | ||||||
Timestamp: uint64(time.Now().Unix()), | ||||||
Result: res, | ||||||
}) | ||||||
if err != nil { | ||||||
return errors.Wrap(err, "failed to set cache") | ||||||
} | ||||||
|
||||||
return nil | ||||||
}) | ||||||
if err != nil { | ||||||
return errors.Wrapf(err, "failed to schedule the task: %s", task.ID()) | ||||||
} | ||||||
} | ||||||
|
||||||
pm.scheduler.StartAsync() | ||||||
return nil | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package perf | ||
|
||
import ( | ||
"context" | ||
) | ||
|
||
type Task interface { | ||
ID() string | ||
Cron() string | ||
Run(ctx context.Context) (interface{}, error) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While this will work perfectly will in Go . I don't like the fact that the output type changes based on the input (one returns a single object, the other returns a list) which makes it annoying to call from languages that supports static type.
This can be solved in 2 possible ways
zos.perf.get
andzos.perf.get_all
A 3rd solution would be always return ALL? and then client can filter the test on client side is also a possible option