mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2025-12-16 04:06:16 +01:00
Move avro into memorystore. Refactor
Does not compile
This commit is contained in:
@@ -246,9 +246,9 @@ func main() {
|
|||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
// Metric Store starts after all flags have been processes
|
// Metric Store starts after all flags have been processes
|
||||||
if config.InternalCCMSFlag {
|
if memorystore.InternalCCMSFlag {
|
||||||
if mscfg := ccconf.GetPackageConfig("metric-store"); mscfg != nil {
|
if mscfg := ccconf.GetPackageConfig("metric-store"); mscfg != nil {
|
||||||
config.InitMetricStore(mscfg)
|
memorystore.InitMetricStore(mscfg)
|
||||||
} else {
|
} else {
|
||||||
cclog.Abort("Metric Store configuration must be present")
|
cclog.Abort("Metric Store configuration must be present")
|
||||||
}
|
}
|
||||||
@@ -257,7 +257,6 @@ func main() {
|
|||||||
}
|
}
|
||||||
archiver.Start(repository.GetJobRepository())
|
archiver.Start(repository.GetJobRepository())
|
||||||
|
|
||||||
// // Comment out
|
|
||||||
taskManager.Start(ccconf.GetPackageConfig("cron"),
|
taskManager.Start(ccconf.GetPackageConfig("cron"),
|
||||||
ccconf.GetPackageConfig("archive"))
|
ccconf.GetPackageConfig("archive"))
|
||||||
|
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ func serverInit() {
|
|||||||
apiHandle.MountConfigApiRoutes(configapi)
|
apiHandle.MountConfigApiRoutes(configapi)
|
||||||
apiHandle.MountFrontendApiRoutes(frontendapi)
|
apiHandle.MountFrontendApiRoutes(frontendapi)
|
||||||
|
|
||||||
if config.InternalCCMSFlag {
|
if memorystore.InternalCCMSFlag {
|
||||||
apiHandle.MountMetricStoreApiRoutes(metricstoreapi)
|
apiHandle.MountMetricStoreApiRoutes(metricstoreapi)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,7 +369,7 @@ func serverShutdown() {
|
|||||||
server.Shutdown(context.Background())
|
server.Shutdown(context.Background())
|
||||||
|
|
||||||
// Archive all the metric store data
|
// Archive all the metric store data
|
||||||
if config.InternalCCMSFlag {
|
if memorystore.InternalCCMSFlag {
|
||||||
memorystore.Shutdown()
|
memorystore.Shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// Use of this source code is governed by a MIT-style
|
// Use of this source code is governed by a MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package memorystore
|
package memorystore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -47,7 +48,7 @@ type ErrorResponse struct {
|
|||||||
Error string `json:"error"` // Error Message
|
Error string `json:"error"` // Error Message
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApiMetricData struct {
|
type APIMetricData struct {
|
||||||
Error *string `json:"error,omitempty"`
|
Error *string `json:"error,omitempty"`
|
||||||
Data schema.FloatArray `json:"data,omitempty"`
|
Data schema.FloatArray `json:"data,omitempty"`
|
||||||
From int64 `json:"from"`
|
From int64 `json:"from"`
|
||||||
@@ -69,7 +70,7 @@ func handleError(err error, statusCode int, rw http.ResponseWriter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Optimize this, just like the stats endpoint!
|
// TODO: Optimize this, just like the stats endpoint!
|
||||||
func (data *ApiMetricData) AddStats() {
|
func (data *APIMetricData) AddStats() {
|
||||||
n := 0
|
n := 0
|
||||||
sum, min, max := 0.0, math.MaxFloat64, -math.MaxFloat64
|
sum, min, max := 0.0, math.MaxFloat64, -math.MaxFloat64
|
||||||
for _, x := range data.Data {
|
for _, x := range data.Data {
|
||||||
@@ -93,7 +94,7 @@ func (data *ApiMetricData) AddStats() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (data *ApiMetricData) ScaleBy(f schema.Float) {
|
func (data *APIMetricData) ScaleBy(f schema.Float) {
|
||||||
if f == 0 || f == 1 {
|
if f == 0 || f == 1 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -106,7 +107,7 @@ func (data *ApiMetricData) ScaleBy(f schema.Float) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (data *ApiMetricData) PadDataWithNull(ms *MemoryStore, from, to int64, metric string) {
|
func (data *APIMetricData) PadDataWithNull(ms *MemoryStore, from, to int64, metric string) {
|
||||||
minfo, ok := ms.Metrics[metric]
|
minfo, ok := ms.Metrics[metric]
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
@@ -115,7 +116,7 @@ func (data *ApiMetricData) PadDataWithNull(ms *MemoryStore, from, to int64, metr
|
|||||||
if (data.From / minfo.Frequency) > (from / minfo.Frequency) {
|
if (data.From / minfo.Frequency) > (from / minfo.Frequency) {
|
||||||
padfront := int((data.From / minfo.Frequency) - (from / minfo.Frequency))
|
padfront := int((data.From / minfo.Frequency) - (from / minfo.Frequency))
|
||||||
ndata := make([]schema.Float, 0, padfront+len(data.Data))
|
ndata := make([]schema.Float, 0, padfront+len(data.Data))
|
||||||
for i := 0; i < padfront; i++ {
|
for range padfront {
|
||||||
ndata = append(ndata, schema.NaN)
|
ndata = append(ndata, schema.NaN)
|
||||||
}
|
}
|
||||||
for j := 0; j < len(data.Data); j++ {
|
for j := 0; j < len(data.Data); j++ {
|
||||||
@@ -218,9 +219,9 @@ func HandleWrite(rw http.ResponseWriter, r *http.Request) {
|
|||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApiQueryRequest struct {
|
type APIQueryRequest struct {
|
||||||
Cluster string `json:"cluster"`
|
Cluster string `json:"cluster"`
|
||||||
Queries []ApiQuery `json:"queries"`
|
Queries []APIQuery `json:"queries"`
|
||||||
ForAllNodes []string `json:"for-all-nodes"`
|
ForAllNodes []string `json:"for-all-nodes"`
|
||||||
From int64 `json:"from"`
|
From int64 `json:"from"`
|
||||||
To int64 `json:"to"`
|
To int64 `json:"to"`
|
||||||
@@ -229,12 +230,12 @@ type ApiQueryRequest struct {
|
|||||||
WithPadding bool `json:"with-padding"`
|
WithPadding bool `json:"with-padding"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApiQueryResponse struct {
|
type APIQueryResponse struct {
|
||||||
Queries []ApiQuery `json:"queries,omitempty"`
|
Queries []APIQuery `json:"queries,omitempty"`
|
||||||
Results [][]ApiMetricData `json:"results"`
|
Results [][]APIMetricData `json:"results"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApiQuery struct {
|
type APIQuery struct {
|
||||||
Type *string `json:"type,omitempty"`
|
Type *string `json:"type,omitempty"`
|
||||||
SubType *string `json:"subtype,omitempty"`
|
SubType *string `json:"subtype,omitempty"`
|
||||||
Metric string `json:"metric"`
|
Metric string `json:"metric"`
|
||||||
@@ -246,22 +247,21 @@ type ApiQuery struct {
|
|||||||
Aggregate bool `json:"aggreg"`
|
Aggregate bool `json:"aggreg"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func FetchData(req ApiQueryRequest) (*ApiQueryResponse, error) {
|
func FetchData(req APIQueryRequest) (*APIQueryResponse, error) {
|
||||||
|
|
||||||
req.WithData = true
|
req.WithData = true
|
||||||
req.WithData = true
|
req.WithData = true
|
||||||
req.WithData = true
|
req.WithData = true
|
||||||
|
|
||||||
ms := GetMemoryStore()
|
ms := GetMemoryStore()
|
||||||
|
|
||||||
response := ApiQueryResponse{
|
response := APIQueryResponse{
|
||||||
Results: make([][]ApiMetricData, 0, len(req.Queries)),
|
Results: make([][]APIMetricData, 0, len(req.Queries)),
|
||||||
}
|
}
|
||||||
if req.ForAllNodes != nil {
|
if req.ForAllNodes != nil {
|
||||||
nodes := ms.ListChildren([]string{req.Cluster})
|
nodes := ms.ListChildren([]string{req.Cluster})
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
for _, metric := range req.ForAllNodes {
|
for _, metric := range req.ForAllNodes {
|
||||||
q := ApiQuery{
|
q := APIQuery{
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
Hostname: node,
|
Hostname: node,
|
||||||
}
|
}
|
||||||
@@ -300,21 +300,21 @@ func FetchData(req ApiQueryRequest) (*ApiQueryResponse, error) {
|
|||||||
}
|
}
|
||||||
sels = append(sels, sel)
|
sels = append(sels, sel)
|
||||||
} else {
|
} else {
|
||||||
for _, typeId := range query.TypeIds {
|
for _, typeID := range query.TypeIds {
|
||||||
if query.SubType != nil {
|
if query.SubType != nil {
|
||||||
for _, subTypeId := range query.SubTypeIds {
|
for _, subTypeID := range query.SubTypeIds {
|
||||||
sels = append(sels, util.Selector{
|
sels = append(sels, util.Selector{
|
||||||
{String: req.Cluster},
|
{String: req.Cluster},
|
||||||
{String: query.Hostname},
|
{String: query.Hostname},
|
||||||
{String: *query.Type + typeId},
|
{String: *query.Type + typeID},
|
||||||
{String: *query.SubType + subTypeId},
|
{String: *query.SubType + subTypeID},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sels = append(sels, util.Selector{
|
sels = append(sels, util.Selector{
|
||||||
{String: req.Cluster},
|
{String: req.Cluster},
|
||||||
{String: query.Hostname},
|
{String: query.Hostname},
|
||||||
{String: *query.Type + typeId},
|
{String: *query.Type + typeID},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -323,12 +323,11 @@ func FetchData(req ApiQueryRequest) (*ApiQueryResponse, error) {
|
|||||||
// log.Printf("query: %#v\n", query)
|
// log.Printf("query: %#v\n", query)
|
||||||
// log.Printf("sels: %#v\n", sels)
|
// log.Printf("sels: %#v\n", sels)
|
||||||
var err error
|
var err error
|
||||||
res := make([]ApiMetricData, 0, len(sels))
|
res := make([]APIMetricData, 0, len(sels))
|
||||||
for _, sel := range sels {
|
for _, sel := range sels {
|
||||||
data := ApiMetricData{}
|
data := APIMetricData{}
|
||||||
|
|
||||||
data.Data, data.From, data.To, data.Resolution, err = ms.Read(sel, query.Metric, req.From, req.To, query.Resolution)
|
data.Data, data.From, data.To, data.Resolution, err = ms.Read(sel, query.Metric, req.From, req.To, query.Resolution)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg := err.Error()
|
msg := err.Error()
|
||||||
data.Error = &msg
|
data.Error = &msg
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// All rights reserved. This file is part of cc-backend.
|
// All rights reserved. This file is part of cc-backend.
|
||||||
// Use of this source code is governed by a MIT-style
|
// Use of this source code is governed by a MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package memorystore
|
package memorystore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -18,14 +19,13 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ClusterCockpit/cc-backend/internal/config"
|
|
||||||
cclog "github.com/ClusterCockpit/cc-lib/ccLogger"
|
cclog "github.com/ClusterCockpit/cc-lib/ccLogger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Archiving(wg *sync.WaitGroup, ctx context.Context) {
|
func Archiving(wg *sync.WaitGroup, ctx context.Context) {
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
d, err := time.ParseDuration(config.MetricStoreKeys.Archive.Interval)
|
d, err := time.ParseDuration(Keys.Archive.Interval)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("[METRICSTORE]> error parsing archive interval duration: %v\n", err)
|
log.Fatalf("[METRICSTORE]> error parsing archive interval duration: %v\n", err)
|
||||||
}
|
}
|
||||||
@@ -46,8 +46,8 @@ func Archiving(wg *sync.WaitGroup, ctx context.Context) {
|
|||||||
case <-ticks:
|
case <-ticks:
|
||||||
t := time.Now().Add(-d)
|
t := time.Now().Add(-d)
|
||||||
log.Printf("[METRICSTORE]> start archiving checkpoints (older than %s)...\n", t.Format(time.RFC3339))
|
log.Printf("[METRICSTORE]> start archiving checkpoints (older than %s)...\n", t.Format(time.RFC3339))
|
||||||
n, err := ArchiveCheckpoints(config.MetricStoreKeys.Checkpoints.RootDir,
|
n, err := ArchiveCheckpoints(Keys.Checkpoints.RootDir,
|
||||||
config.MetricStoreKeys.Archive.RootDir, t.Unix(), config.MetricStoreKeys.Archive.DeleteInstead)
|
Keys.Archive.RootDir, t.Unix(), Keys.Archive.DeleteInstead)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[METRICSTORE]> archiving failed: %s\n", err.Error())
|
log.Printf("[METRICSTORE]> archiving failed: %s\n", err.Error())
|
||||||
@@ -59,7 +59,7 @@ func Archiving(wg *sync.WaitGroup, ctx context.Context) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrNoNewData error = errors.New("all data already archived")
|
var ErrNoNewArchiveData error = errors.New("all data already archived")
|
||||||
|
|
||||||
// ZIP all checkpoint files older than `from` together and write them to the `archiveDir`,
|
// ZIP all checkpoint files older than `from` together and write them to the `archiveDir`,
|
||||||
// deleting them from the `checkpointsDir`.
|
// deleting them from the `checkpointsDir`.
|
||||||
@@ -129,7 +129,7 @@ func archiveCheckpoints(dir string, archiveDir string, from int64, deleteInstead
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
extension := config.MetricStoreKeys.Checkpoints.FileFormat
|
extension := Keys.Checkpoints.FileFormat
|
||||||
files, err := findFiles(entries, from, extension, false)
|
files, err := findFiles(entries, from, extension, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
// All rights reserved. This file is part of cc-backend.
|
// All rights reserved. This file is part of cc-backend.
|
||||||
// Use of this source code is governed by a MIT-style
|
// Use of this source code is governed by a MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
package avro
|
|
||||||
|
package memorystore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@@ -19,12 +20,11 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ClusterCockpit/cc-backend/internal/config"
|
|
||||||
"github.com/ClusterCockpit/cc-lib/schema"
|
"github.com/ClusterCockpit/cc-lib/schema"
|
||||||
"github.com/linkedin/goavro/v2"
|
"github.com/linkedin/goavro/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var NumWorkers int = 4
|
var NumAvroWorkers int = 4
|
||||||
|
|
||||||
var ErrNoNewData error = errors.New("no data in the pool")
|
var ErrNoNewData error = errors.New("no data in the pool")
|
||||||
|
|
||||||
@@ -58,9 +58,9 @@ func (as *AvroStore) ToCheckpoint(dir string, dumpAll bool) (int, error) {
|
|||||||
n, errs := int32(0), int32(0)
|
n, errs := int32(0), int32(0)
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(NumWorkers)
|
wg.Add(NumAvroWorkers)
|
||||||
work := make(chan workItem, NumWorkers*2)
|
work := make(chan workItem, NumAvroWorkers*2)
|
||||||
for range NumWorkers {
|
for range NumAvroWorkers {
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ func (as *AvroStore) ToCheckpoint(dir string, dumpAll bool) (int, error) {
|
|||||||
from := getTimestamp(workItem.dir)
|
from := getTimestamp(workItem.dir)
|
||||||
|
|
||||||
if err := workItem.level.toCheckpoint(workItem.dir, from, dumpAll); err != nil {
|
if err := workItem.level.toCheckpoint(workItem.dir, from, dumpAll); err != nil {
|
||||||
if err == ErrNoNewData {
|
if err == ErrNoNewArchiveData {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +113,7 @@ func getTimestamp(dir string) int64 {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
var maxTs int64 = 0
|
var maxTS int64 = 0
|
||||||
|
|
||||||
if len(files) == 0 {
|
if len(files) == 0 {
|
||||||
return 0
|
return 0
|
||||||
@@ -135,19 +135,19 @@ func getTimestamp(dir string) int64 {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if ts > maxTs {
|
if ts > maxTS {
|
||||||
maxTs = ts
|
maxTS = ts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interval, _ := time.ParseDuration(config.MetricStoreKeys.Checkpoints.Interval)
|
interval, _ := time.ParseDuration(Keys.Checkpoints.Interval)
|
||||||
updateTime := time.Unix(maxTs, 0).Add(interval).Add(time.Duration(CheckpointBufferMinutes-1) * time.Minute).Unix()
|
updateTime := time.Unix(maxTS, 0).Add(interval).Add(time.Duration(CheckpointBufferMinutes-1) * time.Minute).Unix()
|
||||||
|
|
||||||
if updateTime < time.Now().Unix() {
|
if updateTime < time.Now().Unix() {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
return maxTs
|
return maxTS
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *AvroLevel) toCheckpoint(dir string, from int64, dumpAll bool) error {
|
func (l *AvroLevel) toCheckpoint(dir string, from int64, dumpAll bool) error {
|
||||||
@@ -156,27 +156,27 @@ func (l *AvroLevel) toCheckpoint(dir string, from int64, dumpAll bool) error {
|
|||||||
|
|
||||||
// fmt.Printf("Checkpointing directory: %s\n", dir)
|
// fmt.Printf("Checkpointing directory: %s\n", dir)
|
||||||
// filepath contains the resolution
|
// filepath contains the resolution
|
||||||
int_res, _ := strconv.Atoi(path.Base(dir))
|
intRes, _ := strconv.Atoi(path.Base(dir))
|
||||||
|
|
||||||
// find smallest overall timestamp in l.data map and delete it from l.data
|
// find smallest overall timestamp in l.data map and delete it from l.data
|
||||||
minTs := int64(1<<63 - 1)
|
minTS := int64(1<<63 - 1)
|
||||||
for ts, dat := range l.data {
|
for ts, dat := range l.data {
|
||||||
if ts < minTs && len(dat) != 0 {
|
if ts < minTS && len(dat) != 0 {
|
||||||
minTs = ts
|
minTS = ts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if from == 0 && minTs != int64(1<<63-1) {
|
if from == 0 && minTS != int64(1<<63-1) {
|
||||||
from = minTs
|
from = minTS
|
||||||
}
|
}
|
||||||
|
|
||||||
if from == 0 {
|
if from == 0 {
|
||||||
return ErrNoNewData
|
return ErrNoNewArchiveData
|
||||||
}
|
}
|
||||||
|
|
||||||
var schema string
|
var schema string
|
||||||
var codec *goavro.Codec
|
var codec *goavro.Codec
|
||||||
record_list := make([]map[string]any, 0)
|
recordList := make([]map[string]any, 0)
|
||||||
|
|
||||||
var f *os.File
|
var f *os.File
|
||||||
|
|
||||||
@@ -208,19 +208,19 @@ func (l *AvroLevel) toCheckpoint(dir string, from int64, dumpAll bool) error {
|
|||||||
f.Close()
|
f.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
time_ref := time.Now().Add(time.Duration(-CheckpointBufferMinutes+1) * time.Minute).Unix()
|
timeRef := time.Now().Add(time.Duration(-CheckpointBufferMinutes+1) * time.Minute).Unix()
|
||||||
|
|
||||||
if dumpAll {
|
if dumpAll {
|
||||||
time_ref = time.Now().Unix()
|
timeRef = time.Now().Unix()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty values
|
// Empty values
|
||||||
if len(l.data) == 0 {
|
if len(l.data) == 0 {
|
||||||
// we checkpoint avro files every 60 seconds
|
// we checkpoint avro files every 60 seconds
|
||||||
repeat := 60 / int_res
|
repeat := 60 / intRes
|
||||||
|
|
||||||
for range repeat {
|
for range repeat {
|
||||||
record_list = append(record_list, make(map[string]any))
|
recordList = append(recordList, make(map[string]any))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,15 +228,15 @@ func (l *AvroLevel) toCheckpoint(dir string, from int64, dumpAll bool) error {
|
|||||||
|
|
||||||
for ts := range l.data {
|
for ts := range l.data {
|
||||||
flag := false
|
flag := false
|
||||||
if ts < time_ref {
|
if ts < timeRef {
|
||||||
data := l.data[ts]
|
data := l.data[ts]
|
||||||
|
|
||||||
schema_gen, err := generateSchema(data)
|
schemaGen, err := generateSchema(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
flag, schema, err = compareSchema(schema, schema_gen)
|
flag, schema, err = compareSchema(schema, schemaGen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to compare read and generated schema: %v", err)
|
return fmt.Errorf("failed to compare read and generated schema: %v", err)
|
||||||
}
|
}
|
||||||
@@ -262,7 +262,7 @@ func (l *AvroLevel) toCheckpoint(dir string, from int64, dumpAll bool) error {
|
|||||||
return fmt.Errorf("failed to read record: %v", err)
|
return fmt.Errorf("failed to read record: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
record_list = append(record_list, record.(map[string]any))
|
recordList = append(recordList, record.(map[string]any))
|
||||||
}
|
}
|
||||||
|
|
||||||
f.Close()
|
f.Close()
|
||||||
@@ -279,13 +279,13 @@ func (l *AvroLevel) toCheckpoint(dir string, from int64, dumpAll bool) error {
|
|||||||
return fmt.Errorf("failed to create codec after merged schema: %v", err)
|
return fmt.Errorf("failed to create codec after merged schema: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
record_list = append(record_list, generateRecord(data))
|
recordList = append(recordList, generateRecord(data))
|
||||||
delete(l.data, ts)
|
delete(l.data, ts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(record_list) == 0 {
|
if len(recordList) == 0 {
|
||||||
return ErrNoNewData
|
return ErrNoNewArchiveData
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err = os.OpenFile(filePath, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0o644)
|
f, err = os.OpenFile(filePath, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0o644)
|
||||||
@@ -305,7 +305,7 @@ func (l *AvroLevel) toCheckpoint(dir string, from int64, dumpAll bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Append the new record
|
// Append the new record
|
||||||
if err := writer.Append(record_list); err != nil {
|
if err := writer.Append(recordList); err != nil {
|
||||||
return fmt.Errorf("failed to append record: %v", err)
|
return fmt.Errorf("failed to append record: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,12 +401,12 @@ func compareSchema(schemaRead, schemaGen string) (bool, string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Marshal the merged schema back to JSON
|
// Marshal the merged schema back to JSON
|
||||||
mergedSchemaJson, err := json.Marshal(mergedSchema)
|
mergedSchemaJSON, err := json.Marshal(mergedSchema)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, "", fmt.Errorf("failed to marshal merged schema: %v", err)
|
return false, "", fmt.Errorf("failed to marshal merged schema: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, string(mergedSchemaJson), nil
|
return true, string(mergedSchemaJSON), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateSchema(data map[string]schema.Float) (string, error) {
|
func generateSchema(data map[string]schema.Float) (string, error) {
|
||||||
@@ -2,7 +2,8 @@
|
|||||||
// All rights reserved. This file is part of cc-backend.
|
// All rights reserved. This file is part of cc-backend.
|
||||||
// Use of this source code is governed by a MIT-style
|
// Use of this source code is governed by a MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
package avro
|
|
||||||
|
package memorystore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -10,14 +11,12 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/ClusterCockpit/cc-backend/internal/config"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func DataStaging(wg *sync.WaitGroup, ctx context.Context) {
|
func DataStaging(wg *sync.WaitGroup, ctx context.Context) {
|
||||||
// AvroPool is a pool of Avro writers.
|
// AvroPool is a pool of Avro writers.
|
||||||
go func() {
|
go func() {
|
||||||
if config.MetricStoreKeys.Checkpoints.FileFormat == "json" {
|
if Keys.Checkpoints.FileFormat == "json" {
|
||||||
wg.Done() // Mark this goroutine as done
|
wg.Done() // Mark this goroutine as done
|
||||||
return // Exit the goroutine
|
return // Exit the goroutine
|
||||||
}
|
}
|
||||||
@@ -33,7 +32,7 @@ func DataStaging(wg *sync.WaitGroup, ctx context.Context) {
|
|||||||
return
|
return
|
||||||
case val := <-LineProtocolMessages:
|
case val := <-LineProtocolMessages:
|
||||||
// Fetch the frequency of the metric from the global configuration
|
// Fetch the frequency of the metric from the global configuration
|
||||||
freq, err := config.GetMetricFrequency(val.MetricName)
|
freq, err := GetMetricFrequency(val.MetricName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error fetching metric frequency: %s\n", err)
|
log.Printf("Error fetching metric frequency: %s\n", err)
|
||||||
continue
|
continue
|
||||||
@@ -41,8 +40,8 @@ func DataStaging(wg *sync.WaitGroup, ctx context.Context) {
|
|||||||
|
|
||||||
metricName := ""
|
metricName := ""
|
||||||
|
|
||||||
for _, selector_name := range val.Selector {
|
for _, selectorName := range val.Selector {
|
||||||
metricName += selector_name + Delimiter
|
metricName += selectorName + Delimiter
|
||||||
}
|
}
|
||||||
|
|
||||||
metricName += val.MetricName
|
metricName += val.MetricName
|
||||||
@@ -2,7 +2,8 @@
|
|||||||
// All rights reserved. This file is part of cc-backend.
|
// All rights reserved. This file is part of cc-backend.
|
||||||
// Use of this source code is governed by a MIT-style
|
// Use of this source code is governed by a MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
package avro
|
|
||||||
|
package memorystore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
@@ -112,18 +113,18 @@ func (l *AvroLevel) addMetric(metricName string, value schema.Float, timestamp i
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Get the last timestamp
|
// Get the last timestamp
|
||||||
var lastTs int64
|
var lastTS int64
|
||||||
for ts := range l.data {
|
for ts := range l.data {
|
||||||
if ts > lastTs {
|
if ts > lastTS {
|
||||||
lastTs = ts
|
lastTS = ts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Create keys for the next KeyCounter timestamps
|
// Create keys for the next KeyCounter timestamps
|
||||||
l.data[lastTs+int64(Freq)] = make(map[string]schema.Float, 0)
|
l.data[lastTS+int64(Freq)] = make(map[string]schema.Float, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
closestTs := int64(0)
|
closestTS := int64(0)
|
||||||
minDiff := int64(Freq) + 1 // Start with diff just outside the valid range
|
minDiff := int64(Freq) + 1 // Start with diff just outside the valid range
|
||||||
found := false
|
found := false
|
||||||
|
|
||||||
@@ -144,13 +145,13 @@ func (l *AvroLevel) addMetric(metricName string, value schema.Float, timestamp i
|
|||||||
// Check if this is the closest timestamp so far
|
// Check if this is the closest timestamp so far
|
||||||
if Abs(diff) < minDiff {
|
if Abs(diff) < minDiff {
|
||||||
minDiff = Abs(diff)
|
minDiff = Abs(diff)
|
||||||
closestTs = ts
|
closestTS = ts
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if found {
|
if found {
|
||||||
l.data[closestTs][metricName] = value
|
l.data[closestTS][metricName] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,3 +1,8 @@
|
|||||||
|
// Copyright (C) NHR@FAU, University Erlangen-Nuremberg.
|
||||||
|
// All rights reserved. This file is part of cc-backend.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package memorystore
|
package memorystore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -12,14 +17,14 @@ import (
|
|||||||
// in the buffer chain will be created if needed so that no copying
|
// in the buffer chain will be created if needed so that no copying
|
||||||
// of data or reallocation needs to happen on writes.
|
// of data or reallocation needs to happen on writes.
|
||||||
const (
|
const (
|
||||||
BUFFER_CAP int = 512
|
BufferCap int = 512
|
||||||
)
|
)
|
||||||
|
|
||||||
// So that we can reuse allocations
|
// So that we can reuse allocations
|
||||||
var bufferPool sync.Pool = sync.Pool{
|
var bufferPool sync.Pool = sync.Pool{
|
||||||
New: func() interface{} {
|
New: func() any {
|
||||||
return &buffer{
|
return &buffer{
|
||||||
data: make([]schema.Float, 0, BUFFER_CAP),
|
data: make([]schema.Float, 0, BufferCap),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -192,7 +197,7 @@ func (b *buffer) free(t int64) (delme bool, n int) {
|
|||||||
n += m
|
n += m
|
||||||
if delme {
|
if delme {
|
||||||
b.prev.next = nil
|
b.prev.next = nil
|
||||||
if cap(b.prev.data) == BUFFER_CAP {
|
if cap(b.prev.data) == BufferCap {
|
||||||
bufferPool.Put(b.prev)
|
bufferPool.Put(b.prev)
|
||||||
}
|
}
|
||||||
b.prev = nil
|
b.prev = nil
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
// Copyright (C) NHR@FAU, University Erlangen-Nuremberg.
|
||||||
|
// All rights reserved. This file is part of cc-backend.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package memorystore
|
package memorystore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -19,8 +24,6 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ClusterCockpit/cc-backend/internal/avro"
|
|
||||||
"github.com/ClusterCockpit/cc-backend/internal/config"
|
|
||||||
"github.com/ClusterCockpit/cc-lib/schema"
|
"github.com/ClusterCockpit/cc-lib/schema"
|
||||||
"github.com/linkedin/goavro/v2"
|
"github.com/linkedin/goavro/v2"
|
||||||
)
|
)
|
||||||
@@ -44,12 +47,12 @@ var lastCheckpoint time.Time
|
|||||||
func Checkpointing(wg *sync.WaitGroup, ctx context.Context) {
|
func Checkpointing(wg *sync.WaitGroup, ctx context.Context) {
|
||||||
lastCheckpoint = time.Now()
|
lastCheckpoint = time.Now()
|
||||||
|
|
||||||
if config.MetricStoreKeys.Checkpoints.FileFormat == "json" {
|
if Keys.Checkpoints.FileFormat == "json" {
|
||||||
ms := GetMemoryStore()
|
ms := GetMemoryStore()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
d, err := time.ParseDuration(config.MetricStoreKeys.Checkpoints.Interval)
|
d, err := time.ParseDuration(Keys.Checkpoints.Interval)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -70,7 +73,7 @@ func Checkpointing(wg *sync.WaitGroup, ctx context.Context) {
|
|||||||
case <-ticks:
|
case <-ticks:
|
||||||
log.Printf("[METRICSTORE]> start checkpointing (starting at %s)...\n", lastCheckpoint.Format(time.RFC3339))
|
log.Printf("[METRICSTORE]> start checkpointing (starting at %s)...\n", lastCheckpoint.Format(time.RFC3339))
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
n, err := ms.ToCheckpoint(config.MetricStoreKeys.Checkpoints.RootDir,
|
n, err := ms.ToCheckpoint(Keys.Checkpoints.RootDir,
|
||||||
lastCheckpoint.Unix(), now.Unix())
|
lastCheckpoint.Unix(), now.Unix())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[METRICSTORE]> checkpointing failed: %s\n", err.Error())
|
log.Printf("[METRICSTORE]> checkpointing failed: %s\n", err.Error())
|
||||||
@@ -89,9 +92,9 @@ func Checkpointing(wg *sync.WaitGroup, ctx context.Context) {
|
|||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case <-time.After(time.Duration(avro.CheckpointBufferMinutes) * time.Minute):
|
case <-time.After(time.Duration(CheckpointBufferMinutes) * time.Minute):
|
||||||
// This is the first tick untill we collect the data for given minutes.
|
// This is the first tick untill we collect the data for given minutes.
|
||||||
avro.GetAvroStore().ToCheckpoint(config.MetricStoreKeys.Checkpoints.RootDir, false)
|
GetAvroStore().ToCheckpoint(Keys.Checkpoints.RootDir, false)
|
||||||
// log.Printf("Checkpointing %d avro files", count)
|
// log.Printf("Checkpointing %d avro files", count)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -109,7 +112,7 @@ func Checkpointing(wg *sync.WaitGroup, ctx context.Context) {
|
|||||||
return
|
return
|
||||||
case <-ticks:
|
case <-ticks:
|
||||||
// Regular ticks of 1 minute to write data.
|
// Regular ticks of 1 minute to write data.
|
||||||
avro.GetAvroStore().ToCheckpoint(config.MetricStoreKeys.Checkpoints.RootDir, false)
|
GetAvroStore().ToCheckpoint(Keys.Checkpoints.RootDir, false)
|
||||||
// log.Printf("Checkpointing %d avro files", count)
|
// log.Printf("Checkpointing %d avro files", count)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,7 +179,7 @@ func (m *MemoryStore) ToCheckpoint(dir string, from, to int64) (int, error) {
|
|||||||
|
|
||||||
for workItem := range work {
|
for workItem := range work {
|
||||||
if err := workItem.level.toCheckpoint(workItem.dir, from, to, m); err != nil {
|
if err := workItem.level.toCheckpoint(workItem.dir, from, to, m); err != nil {
|
||||||
if err == ErrNoNewData {
|
if err == ErrNoNewArchiveData {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,7 +222,7 @@ func (l *Level) toCheckpointFile(from, to int64, m *MemoryStore) (*CheckpointFil
|
|||||||
}
|
}
|
||||||
|
|
||||||
for metric, minfo := range m.Metrics {
|
for metric, minfo := range m.Metrics {
|
||||||
b := l.metrics[minfo.Offset]
|
b := l.metrics[minfo.offset]
|
||||||
if b == nil {
|
if b == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -278,7 +281,7 @@ func (l *Level) toCheckpoint(dir string, from, to int64, m *MemoryStore) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if cf == nil {
|
if cf == nil {
|
||||||
return ErrNoNewData
|
return ErrNoNewArchiveData
|
||||||
}
|
}
|
||||||
|
|
||||||
filepath := path.Join(dir, fmt.Sprintf("%d.json", from))
|
filepath := path.Join(dir, fmt.Sprintf("%d.json", from))
|
||||||
@@ -376,7 +379,7 @@ done:
|
|||||||
func (m *MemoryStore) FromCheckpointFiles(dir string, from int64) (int, error) {
|
func (m *MemoryStore) FromCheckpointFiles(dir string, from int64) (int, error) {
|
||||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||||
// The directory does not exist, so create it using os.MkdirAll()
|
// The directory does not exist, so create it using os.MkdirAll()
|
||||||
err := os.MkdirAll(dir, 0755) // 0755 sets the permissions for the directory
|
err := os.MkdirAll(dir, 0o755) // 0755 sets the permissions for the directory
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("[METRICSTORE]> Error creating directory: %#v\n", err)
|
log.Fatalf("[METRICSTORE]> Error creating directory: %#v\n", err)
|
||||||
}
|
}
|
||||||
@@ -384,7 +387,7 @@ func (m *MemoryStore) FromCheckpointFiles(dir string, from int64) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Config read (replace with your actual config read)
|
// Config read (replace with your actual config read)
|
||||||
fileFormat := config.MetricStoreKeys.Checkpoints.FileFormat
|
fileFormat := Keys.Checkpoints.FileFormat
|
||||||
if fileFormat == "" {
|
if fileFormat == "" {
|
||||||
fileFormat = "avro"
|
fileFormat = "avro"
|
||||||
}
|
}
|
||||||
@@ -445,10 +448,10 @@ func (l *Level) loadAvroFile(m *MemoryStore, f *os.File, from int64) error {
|
|||||||
return fmt.Errorf("[METRICSTORE]> error while reading avro file (resolution parsing) : %s", err)
|
return fmt.Errorf("[METRICSTORE]> error while reading avro file (resolution parsing) : %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
from_timestamp, err := strconv.ParseInt(fileName[strings.Index(fileName, "_")+1:len(fileName)-5], 10, 64)
|
fromTimestamp, err := strconv.ParseInt(fileName[strings.Index(fileName, "_")+1:len(fileName)-5], 10, 64)
|
||||||
|
|
||||||
// Same logic according to lineprotocol
|
// Same logic according to lineprotocol
|
||||||
from_timestamp -= (resolution / 2)
|
fromTimestamp -= (resolution / 2)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("[METRICSTORE]> error converting timestamp from the avro file : %s", err)
|
return fmt.Errorf("[METRICSTORE]> error converting timestamp from the avro file : %s", err)
|
||||||
@@ -472,7 +475,7 @@ func (l *Level) loadAvroFile(m *MemoryStore, f *os.File, from int64) error {
|
|||||||
return fmt.Errorf("[METRICSTORE]> error while reading avro file : %s", err)
|
return fmt.Errorf("[METRICSTORE]> error while reading avro file : %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
record, ok := datum.(map[string]interface{})
|
record, ok := datum.(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
panic("[METRICSTORE]> failed to assert datum as map[string]interface{}")
|
panic("[METRICSTORE]> failed to assert datum as map[string]interface{}")
|
||||||
}
|
}
|
||||||
@@ -484,16 +487,16 @@ func (l *Level) loadAvroFile(m *MemoryStore, f *os.File, from int64) error {
|
|||||||
recordCounter += 1
|
recordCounter += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
to := (from_timestamp + (recordCounter / (60 / resolution) * 60))
|
to := (fromTimestamp + (recordCounter / (60 / resolution) * 60))
|
||||||
if to < from {
|
if to < from {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, floatArray := range metricsData {
|
for key, floatArray := range metricsData {
|
||||||
metricName := avro.ReplaceKey(key)
|
metricName := ReplaceKey(key)
|
||||||
|
|
||||||
if strings.Contains(metricName, avro.Delimiter) {
|
if strings.Contains(metricName, Delimiter) {
|
||||||
subString := strings.Split(metricName, avro.Delimiter)
|
subString := strings.Split(metricName, Delimiter)
|
||||||
|
|
||||||
lvl := l
|
lvl := l
|
||||||
|
|
||||||
@@ -517,12 +520,12 @@ func (l *Level) loadAvroFile(m *MemoryStore, f *os.File, from int64) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
leafMetricName := subString[len(subString)-1]
|
leafMetricName := subString[len(subString)-1]
|
||||||
err = lvl.createBuffer(m, leafMetricName, floatArray, from_timestamp, resolution)
|
err = lvl.createBuffer(m, leafMetricName, floatArray, fromTimestamp, resolution)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("[METRICSTORE]> error while creating buffers from avroReader : %s", err)
|
return fmt.Errorf("[METRICSTORE]> error while creating buffers from avroReader : %s", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = l.createBuffer(m, metricName, floatArray, from_timestamp, resolution)
|
err = l.createBuffer(m, metricName, floatArray, fromTimestamp, resolution)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("[METRICSTORE]> error while creating buffers from avroReader : %s", err)
|
return fmt.Errorf("[METRICSTORE]> error while creating buffers from avroReader : %s", err)
|
||||||
}
|
}
|
||||||
@@ -551,9 +554,9 @@ func (l *Level) createBuffer(m *MemoryStore, metricName string, floatArray schem
|
|||||||
// return errors.New("Unkown metric: " + name)
|
// return errors.New("Unkown metric: " + name)
|
||||||
}
|
}
|
||||||
|
|
||||||
prev := l.metrics[minfo.Offset]
|
prev := l.metrics[minfo.offset]
|
||||||
if prev == nil {
|
if prev == nil {
|
||||||
l.metrics[minfo.Offset] = b
|
l.metrics[minfo.offset] = b
|
||||||
} else {
|
} else {
|
||||||
if prev.start > b.start {
|
if prev.start > b.start {
|
||||||
return errors.New("wooops")
|
return errors.New("wooops")
|
||||||
@@ -573,12 +576,12 @@ func (l *Level) createBuffer(m *MemoryStore, metricName string, floatArray schem
|
|||||||
prev.data = prev.data[0:len(prev.data):len(prev.data)]
|
prev.data = prev.data[0:len(prev.data):len(prev.data)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
l.metrics[minfo.Offset] = b
|
l.metrics[minfo.offset] = b
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Level) loadJsonFile(m *MemoryStore, f *os.File, from int64) error {
|
func (l *Level) loadJSONFile(m *MemoryStore, f *os.File, from int64) error {
|
||||||
br := bufio.NewReader(f)
|
br := bufio.NewReader(f)
|
||||||
cf := &CheckpointFile{}
|
cf := &CheckpointFile{}
|
||||||
if err := json.NewDecoder(br).Decode(cf); err != nil {
|
if err := json.NewDecoder(br).Decode(cf); err != nil {
|
||||||
@@ -615,9 +618,9 @@ func (l *Level) loadFile(cf *CheckpointFile, m *MemoryStore) error {
|
|||||||
// return errors.New("Unkown metric: " + name)
|
// return errors.New("Unkown metric: " + name)
|
||||||
}
|
}
|
||||||
|
|
||||||
prev := l.metrics[minfo.Offset]
|
prev := l.metrics[minfo.offset]
|
||||||
if prev == nil {
|
if prev == nil {
|
||||||
l.metrics[minfo.Offset] = b
|
l.metrics[minfo.offset] = b
|
||||||
} else {
|
} else {
|
||||||
if prev.start > b.start {
|
if prev.start > b.start {
|
||||||
return errors.New("wooops")
|
return errors.New("wooops")
|
||||||
@@ -626,7 +629,7 @@ func (l *Level) loadFile(cf *CheckpointFile, m *MemoryStore) error {
|
|||||||
b.prev = prev
|
b.prev = prev
|
||||||
prev.next = b
|
prev.next = b
|
||||||
}
|
}
|
||||||
l.metrics[minfo.Offset] = b
|
l.metrics[minfo.offset] = b
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cf.Children) > 0 && l.children == nil {
|
if len(cf.Children) > 0 && l.children == nil {
|
||||||
@@ -690,7 +693,7 @@ func (l *Level) fromCheckpoint(m *MemoryStore, dir string, from int64, extension
|
|||||||
}
|
}
|
||||||
|
|
||||||
loaders := map[string]func(*MemoryStore, *os.File, int64) error{
|
loaders := map[string]func(*MemoryStore, *os.File, int64) error{
|
||||||
"json": l.loadJsonFile,
|
"json": l.loadJSONFile,
|
||||||
"avro": l.loadAvroFile,
|
"avro": l.loadAvroFile,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -736,7 +739,7 @@ func findFiles(direntries []fs.DirEntry, t int64, extension string, findMoreRece
|
|||||||
})
|
})
|
||||||
|
|
||||||
filenames := make([]string, 0)
|
filenames := make([]string, 0)
|
||||||
for i := 0; i < len(direntries); i++ {
|
for i := range direntries {
|
||||||
e := direntries[i]
|
e := direntries[i]
|
||||||
ts1 := nums[e.Name()]
|
ts1 := nums[e.Name()]
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
package config
|
// Copyright (C) NHR@FAU, University Erlangen-Nuremberg.
|
||||||
|
// All rights reserved. This file is part of cc-backend.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package memorystore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ClusterCockpit/cc-backend/internal/config"
|
||||||
cclog "github.com/ClusterCockpit/cc-lib/ccLogger"
|
cclog "github.com/ClusterCockpit/cc-lib/ccLogger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -50,7 +56,7 @@ type NatsConfig struct {
|
|||||||
} `json:"subscriptions"`
|
} `json:"subscriptions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var MetricStoreKeys MetricStoreConfig
|
var Keys MetricStoreConfig
|
||||||
|
|
||||||
// AggregationStrategy for aggregation over multiple values at different cpus/sockets/..., not time!
|
// AggregationStrategy for aggregation over multiple values at different cpus/sockets/..., not time!
|
||||||
type AggregationStrategy int
|
type AggregationStrategy int
|
||||||
@@ -75,24 +81,26 @@ func AssignAggregationStratergy(str string) (AggregationStrategy, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type MetricConfig struct {
|
type MetricConfig struct {
|
||||||
// Interval in seconds at which measurements will arive.
|
// Interval in seconds at which measurements are stored
|
||||||
Frequency int64
|
Frequency int64
|
||||||
|
|
||||||
// Can be 'sum', 'avg' or null. Describes how to aggregate metrics from the same timestep over the hierarchy.
|
// Can be 'sum', 'avg' or null. Describes how to aggregate metrics from the same timestep over the hierarchy.
|
||||||
Aggregation AggregationStrategy
|
Aggregation AggregationStrategy
|
||||||
|
|
||||||
// Private, used internally...
|
// Private, used internally...
|
||||||
Offset int
|
offset int
|
||||||
}
|
}
|
||||||
|
|
||||||
var Metrics map[string]MetricConfig
|
var Metrics map[string]MetricConfig
|
||||||
|
|
||||||
func InitMetricStore(msConfig json.RawMessage) {
|
func InitMetricStore(rawConfig json.RawMessage) {
|
||||||
// Validate(msConfigSchema, msConfig)
|
if rawConfig != nil {
|
||||||
dec := json.NewDecoder(bytes.NewReader(msConfig))
|
config.Validate(configSchema, rawConfig)
|
||||||
// dec.DisallowUnknownFields()
|
dec := json.NewDecoder(bytes.NewReader(rawConfig))
|
||||||
if err := dec.Decode(&MetricStoreKeys); err != nil {
|
// dec.DisallowUnknownFields()
|
||||||
cclog.Abortf("[METRICSTORE]> Metric Store Config Init: Could not decode config file '%s'.\nError: %s\n", msConfig, err.Error())
|
if err := dec.Decode(&Keys); err != nil {
|
||||||
|
cclog.Abortf("[METRICSTORE]> Metric Store Config Init: Could not decode config file '%s'.\nError: %s\n", rawConfig, err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
190
internal/memorystore/configSchema.go
Normal file
190
internal/memorystore/configSchema.go
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
// Copyright (C) NHR@FAU, University Erlangen-Nuremberg.
|
||||||
|
// All rights reserved. This file is part of cc-backend.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package memorystore
|
||||||
|
|
||||||
|
const configSchema = `{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"jobList": {
|
||||||
|
"description": "Job list defaults. Applies to user- and jobs views.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"usePaging": {
|
||||||
|
"description": "If classic paging is used instead of continuous scrolling by default.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"showFootprint": {
|
||||||
|
"description": "If footprint bars are shown as first column by default.",
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nodeList": {
|
||||||
|
"description": "Node list defaults. Applies to node list view.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"usePaging": {
|
||||||
|
"description": "If classic paging is used instead of continuous scrolling by default.",
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"jobView": {
|
||||||
|
"description": "Job view defaults.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"showPolarPlot": {
|
||||||
|
"description": "If the job metric footprints polar plot is shown by default.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"showFootprint": {
|
||||||
|
"description": "If the annotated job metric footprint bars are shown by default.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"showRoofline": {
|
||||||
|
"description": "If the job roofline plot is shown by default.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"showStatTable": {
|
||||||
|
"description": "If the job metric statistics table is shown by default.",
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metricConfig": {
|
||||||
|
"description": "Global initial metric selections for primary views of all clusters.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"jobListMetrics": {
|
||||||
|
"description": "Initial metrics shown for new users in job lists (User and jobs view).",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"jobViewPlotMetrics": {
|
||||||
|
"description": "Initial metrics shown for new users as job view metric plots.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"jobViewTableMetrics": {
|
||||||
|
"description": "Initial metrics shown for new users in job view statistics table.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"clusters": {
|
||||||
|
"description": "Overrides for global defaults by cluster and subcluster.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"description": "The name of the cluster."
|
||||||
|
},
|
||||||
|
"jobListMetrics": {
|
||||||
|
"description": "Initial metrics shown for new users in job lists (User and jobs view) for subcluster.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"jobViewPlotMetrics": {
|
||||||
|
"description": "Initial metrics shown for new users as job view timeplots for subcluster.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"jobViewTableMetrics": {
|
||||||
|
"description": "Initial metrics shown for new users in job view statistics table for subcluster.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"subClusters": {
|
||||||
|
"description": "The array of overrides per subcluster.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"description": "The name of the subcluster.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"jobListMetrics": {
|
||||||
|
"description": "Initial metrics shown for new users in job lists (User and jobs view) for subcluster.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"jobViewPlotMetrics": {
|
||||||
|
"description": "Initial metrics shown for new users as job view timeplots for subcluster.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"jobViewTableMetrics": {
|
||||||
|
"description": "Initial metrics shown for new users in job view statistics table for subcluster.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["name"],
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["name", "subClusters"],
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plotConfiguration": {
|
||||||
|
"description": "Initial settings for plot render options.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"colorBackground": {
|
||||||
|
"description": "If the metric plot backgrounds are initially colored by threshold limits.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"plotsPerRow": {
|
||||||
|
"description": "How many plots are initially rendered in per row. Applies to job, single node, and analysis views.",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"lineWidth": {
|
||||||
|
"description": "Initial thickness of rendered plotlines. Applies to metric plot, job compare plot and roofline.",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"colorScheme": {
|
||||||
|
"description": "Initial colorScheme to be used for metric plots.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
@@ -1,3 +1,8 @@
|
|||||||
|
// Copyright (C) NHR@FAU, University Erlangen-Nuremberg.
|
||||||
|
// All rights reserved. This file is part of cc-backend.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package memorystore
|
package memorystore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -41,7 +46,7 @@ func (l *Level) debugDump(m *MemoryStore, w *bufio.Writer, lvlname string, buf [
|
|||||||
depth += 1
|
depth += 1
|
||||||
objitems := 0
|
objitems := 0
|
||||||
for name, mc := range m.Metrics {
|
for name, mc := range m.Metrics {
|
||||||
if b := l.metrics[mc.Offset]; b != nil {
|
if b := l.metrics[mc.offset]; b != nil {
|
||||||
for i := 0; i < depth; i++ {
|
for i := 0; i < depth; i++ {
|
||||||
buf = append(buf, '\t')
|
buf = append(buf, '\t')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
// Copyright (C) NHR@FAU, University Erlangen-Nuremberg.
|
||||||
|
// All rights reserved. This file is part of cc-backend.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package memorystore
|
package memorystore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -6,29 +11,28 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This is a threshold that allows a node to be healthy with certain number of data points missing.
|
// MaxMissingDataPoints is a threshold that allows a node to be healthy with certain number of data points missing.
|
||||||
// Suppose a node does not receive last 5 data points, then healthCheck endpoint will still say a
|
// Suppose a node does not receive last 5 data points, then healthCheck endpoint will still say a
|
||||||
// node is healthy. Anything more than 5 missing points in metrics of the node will deem the node unhealthy.
|
// node is healthy. Anything more than 5 missing points in metrics of the node will deem the node unhealthy.
|
||||||
const MaxMissingDataPoints int64 = 5
|
const MaxMissingDataPoints int64 = 5
|
||||||
|
|
||||||
// This is a threshold which allows upto certain number of metrics in a node to be unhealthly.
|
// MaxUnhealthyMetrics is a threshold which allows upto certain number of metrics in a node to be unhealthly.
|
||||||
// Works with MaxMissingDataPoints. Say 5 metrics (including submetrics) do not receive the last
|
// Works with MaxMissingDataPoints. Say 5 metrics (including submetrics) do not receive the last
|
||||||
// MaxMissingDataPoints data points, then the node will be deemed healthy. Any more metrics that does
|
// MaxMissingDataPoints data points, then the node will be deemed healthy. Any more metrics that does
|
||||||
// not receive data for MaxMissingDataPoints data points will deem the node unhealthy.
|
// not receive data for MaxMissingDataPoints data points will deem the node unhealthy.
|
||||||
const MaxUnhealthyMetrics int64 = 5
|
const MaxUnhealthyMetrics int64 = 5
|
||||||
|
|
||||||
func (b *buffer) healthCheck() int64 {
|
func (b *buffer) healthCheck() int64 {
|
||||||
|
|
||||||
// Check if the buffer is empty
|
// Check if the buffer is empty
|
||||||
if b.data == nil {
|
if b.data == nil {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer_end := b.start + b.frequency*int64(len(b.data))
|
bufferEnd := b.start + b.frequency*int64(len(b.data))
|
||||||
t := time.Now().Unix()
|
t := time.Now().Unix()
|
||||||
|
|
||||||
// Check if the buffer is too old
|
// Check if the buffer is too old
|
||||||
if t-buffer_end > MaxMissingDataPoints*b.frequency {
|
if t-bufferEnd > MaxMissingDataPoints*b.frequency {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +44,7 @@ func (l *Level) healthCheck(m *MemoryStore, count int64) (int64, error) {
|
|||||||
defer l.lock.RUnlock()
|
defer l.lock.RUnlock()
|
||||||
|
|
||||||
for _, mc := range m.Metrics {
|
for _, mc := range m.Metrics {
|
||||||
if b := l.metrics[mc.Offset]; b != nil {
|
if b := l.metrics[mc.offset]; b != nil {
|
||||||
count += b.healthCheck()
|
count += b.healthCheck()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
// Copyright (C) NHR@FAU, University Erlangen-Nuremberg.
|
||||||
|
// All rights reserved. This file is part of cc-backend.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package memorystore
|
package memorystore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -77,7 +82,7 @@ func (l *Level) free(t int64) (int, error) {
|
|||||||
delme, m := b.free(t)
|
delme, m := b.free(t)
|
||||||
n += m
|
n += m
|
||||||
if delme {
|
if delme {
|
||||||
if cap(b.data) == BUFFER_CAP {
|
if cap(b.data) == BufferCap {
|
||||||
bufferPool.Put(b)
|
bufferPool.Put(b)
|
||||||
}
|
}
|
||||||
l.metrics[i] = nil
|
l.metrics[i] = nil
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
// Copyright (C) NHR@FAU, University Erlangen-Nuremberg.
|
||||||
|
// All rights reserved. This file is part of cc-backend.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package memorystore
|
package memorystore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -7,8 +12,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ClusterCockpit/cc-backend/internal/avro"
|
|
||||||
"github.com/ClusterCockpit/cc-backend/internal/config"
|
|
||||||
"github.com/ClusterCockpit/cc-lib/schema"
|
"github.com/ClusterCockpit/cc-lib/schema"
|
||||||
"github.com/influxdata/line-protocol/v2/lineprotocol"
|
"github.com/influxdata/line-protocol/v2/lineprotocol"
|
||||||
"github.com/nats-io/nats.go"
|
"github.com/nats-io/nats.go"
|
||||||
@@ -77,10 +80,10 @@ import (
|
|||||||
// return nil
|
// return nil
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// Connect to a nats server and subscribe to "updates". This is a blocking
|
// ReceiveNats connects to a nats server and subscribes to "updates". This is a
|
||||||
// function. handleLine will be called for each line recieved via nats.
|
// blocking function. handleLine will be called for each line recieved via
|
||||||
// Send `true` through the done channel for gracefull termination.
|
// nats. Send `true` through the done channel for gracefull termination.
|
||||||
func ReceiveNats(conf *(config.NatsConfig),
|
func ReceiveNats(conf *(NatsConfig),
|
||||||
ms *MemoryStore,
|
ms *MemoryStore,
|
||||||
workers int,
|
workers int,
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
@@ -170,7 +173,7 @@ func reorder(buf, prefix []byte) []byte {
|
|||||||
for i := m - 1; i >= 0; i-- {
|
for i := m - 1; i >= 0; i-- {
|
||||||
buf[i+n] = buf[i]
|
buf[i+n] = buf[i]
|
||||||
}
|
}
|
||||||
for i := 0; i < n; i++ {
|
for i := range n {
|
||||||
buf[i] = prefix[i]
|
buf[i] = prefix[i]
|
||||||
}
|
}
|
||||||
return buf
|
return buf
|
||||||
@@ -329,14 +332,15 @@ func decodeLine(dec *lineprotocol.Decoder,
|
|||||||
|
|
||||||
time := t.Unix()
|
time := t.Unix()
|
||||||
|
|
||||||
if config.MetricStoreKeys.Checkpoints.FileFormat != "json" {
|
if Keys.Checkpoints.FileFormat != "json" {
|
||||||
avro.LineProtocolMessages <- &avro.AvroStruct{
|
LineProtocolMessages <- &AvroStruct{
|
||||||
MetricName: string(metricBuf),
|
MetricName: string(metricBuf),
|
||||||
Cluster: cluster,
|
Cluster: cluster,
|
||||||
Node: host,
|
Node: host,
|
||||||
Selector: append([]string{}, selector...),
|
Selector: append([]string{}, selector...),
|
||||||
Value: metric.Value,
|
Value: metric.Value,
|
||||||
Timestamp: time}
|
Timestamp: time,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ms.WriteToLevel(lvl, selector, time, []Metric{metric}); err != nil {
|
if err := ms.WriteToLevel(lvl, selector, time, []Metric{metric}); err != nil {
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
// Copyright (C) NHR@FAU, University Erlangen-Nuremberg.
|
||||||
|
// All rights reserved. This file is part of cc-backend.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package memorystore
|
package memorystore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -11,8 +16,6 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ClusterCockpit/cc-backend/internal/avro"
|
|
||||||
"github.com/ClusterCockpit/cc-backend/internal/config"
|
|
||||||
"github.com/ClusterCockpit/cc-lib/resampler"
|
"github.com/ClusterCockpit/cc-lib/resampler"
|
||||||
"github.com/ClusterCockpit/cc-lib/runtimeEnv"
|
"github.com/ClusterCockpit/cc-lib/runtimeEnv"
|
||||||
"github.com/ClusterCockpit/cc-lib/schema"
|
"github.com/ClusterCockpit/cc-lib/schema"
|
||||||
@@ -30,39 +33,36 @@ var NumWorkers int = 4
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
maxWorkers := 10
|
maxWorkers := 10
|
||||||
NumWorkers = runtime.NumCPU()/2 + 1
|
NumWorkers = min(runtime.NumCPU()/2+1, maxWorkers)
|
||||||
if NumWorkers > maxWorkers {
|
|
||||||
NumWorkers = maxWorkers
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Metric struct {
|
type Metric struct {
|
||||||
Name string
|
Name string
|
||||||
Value schema.Float
|
Value schema.Float
|
||||||
MetricConfig config.MetricConfig
|
MetricConfig MetricConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type MemoryStore struct {
|
type MemoryStore struct {
|
||||||
Metrics map[string]config.MetricConfig
|
Metrics map[string]MetricConfig
|
||||||
root Level
|
root Level
|
||||||
}
|
}
|
||||||
|
|
||||||
func Init(wg *sync.WaitGroup) {
|
func Init(wg *sync.WaitGroup) {
|
||||||
startupTime := time.Now()
|
startupTime := time.Now()
|
||||||
|
|
||||||
//Pass the config.MetricStoreKeys
|
// Pass the config.MetricStoreKeys
|
||||||
InitMetrics(config.Metrics)
|
InitMetrics(Metrics)
|
||||||
|
|
||||||
ms := GetMemoryStore()
|
ms := GetMemoryStore()
|
||||||
|
|
||||||
d, err := time.ParseDuration(config.MetricStoreKeys.Checkpoints.Restore)
|
d, err := time.ParseDuration(Keys.Checkpoints.Restore)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreFrom := startupTime.Add(-d)
|
restoreFrom := startupTime.Add(-d)
|
||||||
log.Printf("[METRICSTORE]> Loading checkpoints newer than %s\n", restoreFrom.Format(time.RFC3339))
|
log.Printf("[METRICSTORE]> Loading checkpoints newer than %s\n", restoreFrom.Format(time.RFC3339))
|
||||||
files, err := ms.FromCheckpointFiles(config.MetricStoreKeys.Checkpoints.RootDir, restoreFrom.Unix())
|
files, err := ms.FromCheckpointFiles(Keys.Checkpoints.RootDir, restoreFrom.Unix())
|
||||||
loadedData := ms.SizeInBytes() / 1024 / 1024 // In MB
|
loadedData := ms.SizeInBytes() / 1024 / 1024 // In MB
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("[METRICSTORE]> Loading checkpoints failed: %s\n", err.Error())
|
log.Fatalf("[METRICSTORE]> Loading checkpoints failed: %s\n", err.Error())
|
||||||
@@ -85,7 +85,7 @@ func Init(wg *sync.WaitGroup) {
|
|||||||
Retention(wg, ctx)
|
Retention(wg, ctx)
|
||||||
Checkpointing(wg, ctx)
|
Checkpointing(wg, ctx)
|
||||||
Archiving(wg, ctx)
|
Archiving(wg, ctx)
|
||||||
avro.DataStaging(wg, ctx)
|
DataStaging(wg, ctx)
|
||||||
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
sigs := make(chan os.Signal, 1)
|
sigs := make(chan os.Signal, 1)
|
||||||
@@ -97,8 +97,8 @@ func Init(wg *sync.WaitGroup) {
|
|||||||
shutdown()
|
shutdown()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if config.MetricStoreKeys.Nats != nil {
|
if Keys.Nats != nil {
|
||||||
for _, natsConf := range config.MetricStoreKeys.Nats {
|
for _, natsConf := range Keys.Nats {
|
||||||
// TODO: When multiple nats configs share a URL, do a single connect.
|
// TODO: When multiple nats configs share a URL, do a single connect.
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
nc := natsConf
|
nc := natsConf
|
||||||
@@ -114,9 +114,9 @@ func Init(wg *sync.WaitGroup) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new, initialized instance of a MemoryStore.
|
// InitMetrics creates a new, initialized instance of a MemoryStore.
|
||||||
// Will panic if values in the metric configurations are invalid.
|
// Will panic if values in the metric configurations are invalid.
|
||||||
func InitMetrics(metrics map[string]config.MetricConfig) {
|
func InitMetrics(metrics map[string]MetricConfig) {
|
||||||
singleton.Do(func() {
|
singleton.Do(func() {
|
||||||
offset := 0
|
offset := 0
|
||||||
for key, cfg := range metrics {
|
for key, cfg := range metrics {
|
||||||
@@ -124,10 +124,10 @@ func InitMetrics(metrics map[string]config.MetricConfig) {
|
|||||||
panic("[METRICSTORE]> invalid frequency")
|
panic("[METRICSTORE]> invalid frequency")
|
||||||
}
|
}
|
||||||
|
|
||||||
metrics[key] = config.MetricConfig{
|
metrics[key] = MetricConfig{
|
||||||
Frequency: cfg.Frequency,
|
Frequency: cfg.Frequency,
|
||||||
Aggregation: cfg.Aggregation,
|
Aggregation: cfg.Aggregation,
|
||||||
Offset: offset,
|
offset: offset,
|
||||||
}
|
}
|
||||||
offset += 1
|
offset += 1
|
||||||
}
|
}
|
||||||
@@ -151,17 +151,17 @@ func GetMemoryStore() *MemoryStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Shutdown() {
|
func Shutdown() {
|
||||||
log.Printf("[METRICSTORE]> Writing to '%s'...\n", config.MetricStoreKeys.Checkpoints.RootDir)
|
log.Printf("[METRICSTORE]> Writing to '%s'...\n", Keys.Checkpoints.RootDir)
|
||||||
var files int
|
var files int
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
ms := GetMemoryStore()
|
ms := GetMemoryStore()
|
||||||
|
|
||||||
if config.MetricStoreKeys.Checkpoints.FileFormat == "json" {
|
if Keys.Checkpoints.FileFormat == "json" {
|
||||||
files, err = ms.ToCheckpoint(config.MetricStoreKeys.Checkpoints.RootDir, lastCheckpoint.Unix(), time.Now().Unix())
|
files, err = ms.ToCheckpoint(Keys.Checkpoints.RootDir, lastCheckpoint.Unix(), time.Now().Unix())
|
||||||
} else {
|
} else {
|
||||||
files, err = avro.GetAvroStore().ToCheckpoint(config.MetricStoreKeys.Checkpoints.RootDir, true)
|
files, err = GetAvroStore().ToCheckpoint(Keys.Checkpoints.RootDir, true)
|
||||||
close(avro.LineProtocolMessages)
|
close(LineProtocolMessages)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -234,7 +234,7 @@ func Shutdown() {
|
|||||||
|
|
||||||
func getName(m *MemoryStore, i int) string {
|
func getName(m *MemoryStore, i int) string {
|
||||||
for key, val := range m.Metrics {
|
for key, val := range m.Metrics {
|
||||||
if val.Offset == i {
|
if val.offset == i {
|
||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,7 +246,7 @@ func Retention(wg *sync.WaitGroup, ctx context.Context) {
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
d, err := time.ParseDuration(config.MetricStoreKeys.RetentionInMemory)
|
d, err := time.ParseDuration(Keys.RetentionInMemory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -311,11 +311,11 @@ func (m *MemoryStore) WriteToLevel(l *Level, selector []string, ts int64, metric
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
b := l.metrics[metric.MetricConfig.Offset]
|
b := l.metrics[metric.MetricConfig.offset]
|
||||||
if b == nil {
|
if b == nil {
|
||||||
// First write to this metric and level
|
// First write to this metric and level
|
||||||
b = newBuffer(ts, metric.MetricConfig.Frequency)
|
b = newBuffer(ts, metric.MetricConfig.Frequency)
|
||||||
l.metrics[metric.MetricConfig.Offset] = b
|
l.metrics[metric.MetricConfig.offset] = b
|
||||||
}
|
}
|
||||||
|
|
||||||
nb, err := b.write(ts, metric.Value)
|
nb, err := b.write(ts, metric.Value)
|
||||||
@@ -325,7 +325,7 @@ func (m *MemoryStore) WriteToLevel(l *Level, selector []string, ts int64, metric
|
|||||||
|
|
||||||
// Last write created a new buffer...
|
// Last write created a new buffer...
|
||||||
if b != nb {
|
if b != nb {
|
||||||
l.metrics[metric.MetricConfig.Offset] = nb
|
l.metrics[metric.MetricConfig.offset] = nb
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -337,17 +337,17 @@ func (m *MemoryStore) WriteToLevel(l *Level, selector []string, ts int64, metric
|
|||||||
// the range asked for if no data was available.
|
// the range asked for if no data was available.
|
||||||
func (m *MemoryStore) Read(selector util.Selector, metric string, from, to, resolution int64) ([]schema.Float, int64, int64, int64, error) {
|
func (m *MemoryStore) Read(selector util.Selector, metric string, from, to, resolution int64) ([]schema.Float, int64, int64, int64, error) {
|
||||||
if from > to {
|
if from > to {
|
||||||
return nil, 0, 0, 0, errors.New("[METRICSTORE]> invalid time range\n")
|
return nil, 0, 0, 0, errors.New("[METRICSTORE]> invalid time range")
|
||||||
}
|
}
|
||||||
|
|
||||||
minfo, ok := m.Metrics[metric]
|
minfo, ok := m.Metrics[metric]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, 0, 0, 0, errors.New("[METRICSTORE]> unkown metric: \n" + metric)
|
return nil, 0, 0, 0, errors.New("[METRICSTORE]> unkown metric: " + metric)
|
||||||
}
|
}
|
||||||
|
|
||||||
n, data := 0, make([]schema.Float, (to-from)/minfo.Frequency+1)
|
n, data := 0, make([]schema.Float, (to-from)/minfo.Frequency+1)
|
||||||
|
|
||||||
err := m.root.findBuffers(selector, minfo.Offset, func(b *buffer) error {
|
err := m.root.findBuffers(selector, minfo.offset, func(b *buffer) error {
|
||||||
cdata, cfrom, cto, err := b.read(from, to, data)
|
cdata, cfrom, cto, err := b.read(from, to, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -381,14 +381,14 @@ func (m *MemoryStore) Read(selector util.Selector, metric string, from, to, reso
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, 0, 0, err
|
return nil, 0, 0, 0, err
|
||||||
} else if n == 0 {
|
} else if n == 0 {
|
||||||
return nil, 0, 0, 0, errors.New("[METRICSTORE]> metric or host not found\n")
|
return nil, 0, 0, 0, errors.New("[METRICSTORE]> metric or host not found")
|
||||||
} else if n > 1 {
|
} else if n > 1 {
|
||||||
if minfo.Aggregation == config.AvgAggregation {
|
if minfo.Aggregation == AvgAggregation {
|
||||||
normalize := 1. / schema.Float(n)
|
normalize := 1. / schema.Float(n)
|
||||||
for i := 0; i < len(data); i++ {
|
for i := 0; i < len(data); i++ {
|
||||||
data[i] *= normalize
|
data[i] *= normalize
|
||||||
}
|
}
|
||||||
} else if minfo.Aggregation != config.SumAggregation {
|
} else if minfo.Aggregation != SumAggregation {
|
||||||
return nil, 0, 0, 0, errors.New("[METRICSTORE]> invalid aggregation")
|
return nil, 0, 0, 0, errors.New("[METRICSTORE]> invalid aggregation")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
|
// Copyright (C) NHR@FAU, University Erlangen-Nuremberg.
|
||||||
|
// All rights reserved. This file is part of cc-backend.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package memorystore
|
package memorystore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/ClusterCockpit/cc-backend/internal/config"
|
|
||||||
"github.com/ClusterCockpit/cc-lib/util"
|
"github.com/ClusterCockpit/cc-lib/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -78,7 +82,7 @@ func (m *MemoryStore) Stats(selector util.Selector, metric string, from, to int6
|
|||||||
|
|
||||||
n, samples := 0, 0
|
n, samples := 0, 0
|
||||||
avg, min, max := util.Float(0), math.MaxFloat32, -math.MaxFloat32
|
avg, min, max := util.Float(0), math.MaxFloat32, -math.MaxFloat32
|
||||||
err := m.root.findBuffers(selector, minfo.Offset, func(b *buffer) error {
|
err := m.root.findBuffers(selector, minfo.offset, func(b *buffer) error {
|
||||||
stats, cfrom, cto, err := b.stats(from, to)
|
stats, cfrom, cto, err := b.stats(from, to)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -105,9 +109,9 @@ func (m *MemoryStore) Stats(selector util.Selector, metric string, from, to int6
|
|||||||
return nil, 0, 0, ErrNoData
|
return nil, 0, 0, ErrNoData
|
||||||
}
|
}
|
||||||
|
|
||||||
if minfo.Aggregation == config.AvgAggregation {
|
if minfo.Aggregation == AvgAggregation {
|
||||||
avg /= util.Float(n)
|
avg /= util.Float(n)
|
||||||
} else if n > 1 && minfo.Aggregation != config.SumAggregation {
|
} else if n > 1 && minfo.Aggregation != SumAggregation {
|
||||||
return nil, 0, 0, errors.New("invalid aggregation")
|
return nil, 0, 0, errors.New("invalid aggregation")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func (ccms *CCMetricStoreInternal) LoadData(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
req := memorystore.ApiQueryRequest{
|
req := memorystore.APIQueryRequest{
|
||||||
Cluster: job.Cluster,
|
Cluster: job.Cluster,
|
||||||
From: job.StartTime,
|
From: job.StartTime,
|
||||||
To: job.StartTime + int64(job.Duration),
|
To: job.StartTime + int64(job.Duration),
|
||||||
@@ -156,8 +156,8 @@ func (ccms *CCMetricStoreInternal) buildQueries(
|
|||||||
metrics []string,
|
metrics []string,
|
||||||
scopes []schema.MetricScope,
|
scopes []schema.MetricScope,
|
||||||
resolution int64,
|
resolution int64,
|
||||||
) ([]memorystore.ApiQuery, []schema.MetricScope, error) {
|
) ([]memorystore.APIQuery, []schema.MetricScope, error) {
|
||||||
queries := make([]memorystore.ApiQuery, 0, len(metrics)*len(scopes)*len(job.Resources))
|
queries := make([]memorystore.APIQuery, 0, len(metrics)*len(scopes)*len(job.Resources))
|
||||||
assignedScope := []schema.MetricScope{}
|
assignedScope := []schema.MetricScope{}
|
||||||
|
|
||||||
subcluster, scerr := archive.GetSubCluster(job.Cluster, job.SubCluster)
|
subcluster, scerr := archive.GetSubCluster(job.Cluster, job.SubCluster)
|
||||||
@@ -219,7 +219,7 @@ func (ccms *CCMetricStoreInternal) buildQueries(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
queries = append(queries, memorystore.ApiQuery{
|
queries = append(queries, memorystore.APIQuery{
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
Hostname: host.Hostname,
|
Hostname: host.Hostname,
|
||||||
Aggregate: false,
|
Aggregate: false,
|
||||||
@@ -237,7 +237,7 @@ func (ccms *CCMetricStoreInternal) buildQueries(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
queries = append(queries, memorystore.ApiQuery{
|
queries = append(queries, memorystore.APIQuery{
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
Hostname: host.Hostname,
|
Hostname: host.Hostname,
|
||||||
Aggregate: true,
|
Aggregate: true,
|
||||||
@@ -251,7 +251,7 @@ func (ccms *CCMetricStoreInternal) buildQueries(
|
|||||||
|
|
||||||
// HWThread -> HWThead
|
// HWThread -> HWThead
|
||||||
if nativeScope == schema.MetricScopeHWThread && scope == schema.MetricScopeHWThread {
|
if nativeScope == schema.MetricScopeHWThread && scope == schema.MetricScopeHWThread {
|
||||||
queries = append(queries, memorystore.ApiQuery{
|
queries = append(queries, memorystore.APIQuery{
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
Hostname: host.Hostname,
|
Hostname: host.Hostname,
|
||||||
Aggregate: false,
|
Aggregate: false,
|
||||||
@@ -267,7 +267,7 @@ func (ccms *CCMetricStoreInternal) buildQueries(
|
|||||||
if nativeScope == schema.MetricScopeHWThread && scope == schema.MetricScopeCore {
|
if nativeScope == schema.MetricScopeHWThread && scope == schema.MetricScopeCore {
|
||||||
cores, _ := topology.GetCoresFromHWThreads(hwthreads)
|
cores, _ := topology.GetCoresFromHWThreads(hwthreads)
|
||||||
for _, core := range cores {
|
for _, core := range cores {
|
||||||
queries = append(queries, memorystore.ApiQuery{
|
queries = append(queries, memorystore.APIQuery{
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
Hostname: host.Hostname,
|
Hostname: host.Hostname,
|
||||||
Aggregate: true,
|
Aggregate: true,
|
||||||
@@ -284,7 +284,7 @@ func (ccms *CCMetricStoreInternal) buildQueries(
|
|||||||
if nativeScope == schema.MetricScopeHWThread && scope == schema.MetricScopeSocket {
|
if nativeScope == schema.MetricScopeHWThread && scope == schema.MetricScopeSocket {
|
||||||
sockets, _ := topology.GetSocketsFromHWThreads(hwthreads)
|
sockets, _ := topology.GetSocketsFromHWThreads(hwthreads)
|
||||||
for _, socket := range sockets {
|
for _, socket := range sockets {
|
||||||
queries = append(queries, memorystore.ApiQuery{
|
queries = append(queries, memorystore.APIQuery{
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
Hostname: host.Hostname,
|
Hostname: host.Hostname,
|
||||||
Aggregate: true,
|
Aggregate: true,
|
||||||
@@ -299,7 +299,7 @@ func (ccms *CCMetricStoreInternal) buildQueries(
|
|||||||
|
|
||||||
// HWThread -> Node
|
// HWThread -> Node
|
||||||
if nativeScope == schema.MetricScopeHWThread && scope == schema.MetricScopeNode {
|
if nativeScope == schema.MetricScopeHWThread && scope == schema.MetricScopeNode {
|
||||||
queries = append(queries, memorystore.ApiQuery{
|
queries = append(queries, memorystore.APIQuery{
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
Hostname: host.Hostname,
|
Hostname: host.Hostname,
|
||||||
Aggregate: true,
|
Aggregate: true,
|
||||||
@@ -314,7 +314,7 @@ func (ccms *CCMetricStoreInternal) buildQueries(
|
|||||||
// Core -> Core
|
// Core -> Core
|
||||||
if nativeScope == schema.MetricScopeCore && scope == schema.MetricScopeCore {
|
if nativeScope == schema.MetricScopeCore && scope == schema.MetricScopeCore {
|
||||||
cores, _ := topology.GetCoresFromHWThreads(hwthreads)
|
cores, _ := topology.GetCoresFromHWThreads(hwthreads)
|
||||||
queries = append(queries, memorystore.ApiQuery{
|
queries = append(queries, memorystore.APIQuery{
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
Hostname: host.Hostname,
|
Hostname: host.Hostname,
|
||||||
Aggregate: false,
|
Aggregate: false,
|
||||||
@@ -330,7 +330,7 @@ func (ccms *CCMetricStoreInternal) buildQueries(
|
|||||||
if nativeScope == schema.MetricScopeCore && scope == schema.MetricScopeSocket {
|
if nativeScope == schema.MetricScopeCore && scope == schema.MetricScopeSocket {
|
||||||
sockets, _ := topology.GetSocketsFromCores(hwthreads)
|
sockets, _ := topology.GetSocketsFromCores(hwthreads)
|
||||||
for _, socket := range sockets {
|
for _, socket := range sockets {
|
||||||
queries = append(queries, memorystore.ApiQuery{
|
queries = append(queries, memorystore.APIQuery{
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
Hostname: host.Hostname,
|
Hostname: host.Hostname,
|
||||||
Aggregate: true,
|
Aggregate: true,
|
||||||
@@ -346,7 +346,7 @@ func (ccms *CCMetricStoreInternal) buildQueries(
|
|||||||
// Core -> Node
|
// Core -> Node
|
||||||
if nativeScope == schema.MetricScopeCore && scope == schema.MetricScopeNode {
|
if nativeScope == schema.MetricScopeCore && scope == schema.MetricScopeNode {
|
||||||
cores, _ := topology.GetCoresFromHWThreads(hwthreads)
|
cores, _ := topology.GetCoresFromHWThreads(hwthreads)
|
||||||
queries = append(queries, memorystore.ApiQuery{
|
queries = append(queries, memorystore.APIQuery{
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
Hostname: host.Hostname,
|
Hostname: host.Hostname,
|
||||||
Aggregate: true,
|
Aggregate: true,
|
||||||
@@ -361,7 +361,7 @@ func (ccms *CCMetricStoreInternal) buildQueries(
|
|||||||
// MemoryDomain -> MemoryDomain
|
// MemoryDomain -> MemoryDomain
|
||||||
if nativeScope == schema.MetricScopeMemoryDomain && scope == schema.MetricScopeMemoryDomain {
|
if nativeScope == schema.MetricScopeMemoryDomain && scope == schema.MetricScopeMemoryDomain {
|
||||||
sockets, _ := topology.GetMemoryDomainsFromHWThreads(hwthreads)
|
sockets, _ := topology.GetMemoryDomainsFromHWThreads(hwthreads)
|
||||||
queries = append(queries, memorystore.ApiQuery{
|
queries = append(queries, memorystore.APIQuery{
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
Hostname: host.Hostname,
|
Hostname: host.Hostname,
|
||||||
Aggregate: false,
|
Aggregate: false,
|
||||||
@@ -376,7 +376,7 @@ func (ccms *CCMetricStoreInternal) buildQueries(
|
|||||||
// MemoryDoman -> Node
|
// MemoryDoman -> Node
|
||||||
if nativeScope == schema.MetricScopeMemoryDomain && scope == schema.MetricScopeNode {
|
if nativeScope == schema.MetricScopeMemoryDomain && scope == schema.MetricScopeNode {
|
||||||
sockets, _ := topology.GetMemoryDomainsFromHWThreads(hwthreads)
|
sockets, _ := topology.GetMemoryDomainsFromHWThreads(hwthreads)
|
||||||
queries = append(queries, memorystore.ApiQuery{
|
queries = append(queries, memorystore.APIQuery{
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
Hostname: host.Hostname,
|
Hostname: host.Hostname,
|
||||||
Aggregate: true,
|
Aggregate: true,
|
||||||
@@ -391,7 +391,7 @@ func (ccms *CCMetricStoreInternal) buildQueries(
|
|||||||
// Socket -> Socket
|
// Socket -> Socket
|
||||||
if nativeScope == schema.MetricScopeSocket && scope == schema.MetricScopeSocket {
|
if nativeScope == schema.MetricScopeSocket && scope == schema.MetricScopeSocket {
|
||||||
sockets, _ := topology.GetSocketsFromHWThreads(hwthreads)
|
sockets, _ := topology.GetSocketsFromHWThreads(hwthreads)
|
||||||
queries = append(queries, memorystore.ApiQuery{
|
queries = append(queries, memorystore.APIQuery{
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
Hostname: host.Hostname,
|
Hostname: host.Hostname,
|
||||||
Aggregate: false,
|
Aggregate: false,
|
||||||
@@ -406,7 +406,7 @@ func (ccms *CCMetricStoreInternal) buildQueries(
|
|||||||
// Socket -> Node
|
// Socket -> Node
|
||||||
if nativeScope == schema.MetricScopeSocket && scope == schema.MetricScopeNode {
|
if nativeScope == schema.MetricScopeSocket && scope == schema.MetricScopeNode {
|
||||||
sockets, _ := topology.GetSocketsFromHWThreads(hwthreads)
|
sockets, _ := topology.GetSocketsFromHWThreads(hwthreads)
|
||||||
queries = append(queries, memorystore.ApiQuery{
|
queries = append(queries, memorystore.APIQuery{
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
Hostname: host.Hostname,
|
Hostname: host.Hostname,
|
||||||
Aggregate: true,
|
Aggregate: true,
|
||||||
@@ -420,7 +420,7 @@ func (ccms *CCMetricStoreInternal) buildQueries(
|
|||||||
|
|
||||||
// Node -> Node
|
// Node -> Node
|
||||||
if nativeScope == schema.MetricScopeNode && scope == schema.MetricScopeNode {
|
if nativeScope == schema.MetricScopeNode && scope == schema.MetricScopeNode {
|
||||||
queries = append(queries, memorystore.ApiQuery{
|
queries = append(queries, memorystore.APIQuery{
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
Hostname: host.Hostname,
|
Hostname: host.Hostname,
|
||||||
Resolution: resolution,
|
Resolution: resolution,
|
||||||
@@ -448,7 +448,7 @@ func (ccms *CCMetricStoreInternal) LoadStats(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
req := memorystore.ApiQueryRequest{
|
req := memorystore.APIQueryRequest{
|
||||||
Cluster: job.Cluster,
|
Cluster: job.Cluster,
|
||||||
From: job.StartTime,
|
From: job.StartTime,
|
||||||
To: job.StartTime + int64(job.Duration),
|
To: job.StartTime + int64(job.Duration),
|
||||||
@@ -507,7 +507,7 @@ func (ccms *CCMetricStoreInternal) LoadScopedStats(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
req := memorystore.ApiQueryRequest{
|
req := memorystore.APIQueryRequest{
|
||||||
Cluster: job.Cluster,
|
Cluster: job.Cluster,
|
||||||
From: job.StartTime,
|
From: job.StartTime,
|
||||||
To: job.StartTime + int64(job.Duration),
|
To: job.StartTime + int64(job.Duration),
|
||||||
@@ -593,7 +593,7 @@ func (ccms *CCMetricStoreInternal) LoadNodeData(
|
|||||||
from, to time.Time,
|
from, to time.Time,
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
) (map[string]map[string][]*schema.JobMetric, error) {
|
) (map[string]map[string][]*schema.JobMetric, error) {
|
||||||
req := memorystore.ApiQueryRequest{
|
req := memorystore.APIQueryRequest{
|
||||||
Cluster: cluster,
|
Cluster: cluster,
|
||||||
From: from.Unix(),
|
From: from.Unix(),
|
||||||
To: to.Unix(),
|
To: to.Unix(),
|
||||||
@@ -606,7 +606,7 @@ func (ccms *CCMetricStoreInternal) LoadNodeData(
|
|||||||
} else {
|
} else {
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
for _, metric := range metrics {
|
for _, metric := range metrics {
|
||||||
req.Queries = append(req.Queries, memorystore.ApiQuery{
|
req.Queries = append(req.Queries, memorystore.APIQuery{
|
||||||
Hostname: node,
|
Hostname: node,
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
Resolution: 0, // Default for Node Queries: Will return metric $Timestep Resolution
|
Resolution: 0, // Default for Node Queries: Will return metric $Timestep Resolution
|
||||||
@@ -624,7 +624,7 @@ func (ccms *CCMetricStoreInternal) LoadNodeData(
|
|||||||
var errors []string
|
var errors []string
|
||||||
data := make(map[string]map[string][]*schema.JobMetric)
|
data := make(map[string]map[string][]*schema.JobMetric)
|
||||||
for i, res := range resBody.Results {
|
for i, res := range resBody.Results {
|
||||||
var query memorystore.ApiQuery
|
var query memorystore.APIQuery
|
||||||
if resBody.Queries != nil {
|
if resBody.Queries != nil {
|
||||||
query = resBody.Queries[i]
|
query = resBody.Queries[i]
|
||||||
} else {
|
} else {
|
||||||
@@ -737,7 +737,7 @@ func (ccms *CCMetricStoreInternal) LoadNodeListData(
|
|||||||
return nil, totalNodes, hasNextPage, err
|
return nil, totalNodes, hasNextPage, err
|
||||||
}
|
}
|
||||||
|
|
||||||
req := memorystore.ApiQueryRequest{
|
req := memorystore.APIQueryRequest{
|
||||||
Cluster: cluster,
|
Cluster: cluster,
|
||||||
Queries: queries,
|
Queries: queries,
|
||||||
From: from.Unix(),
|
From: from.Unix(),
|
||||||
@@ -755,7 +755,7 @@ func (ccms *CCMetricStoreInternal) LoadNodeListData(
|
|||||||
var errors []string
|
var errors []string
|
||||||
data := make(map[string]schema.JobData)
|
data := make(map[string]schema.JobData)
|
||||||
for i, row := range resBody.Results {
|
for i, row := range resBody.Results {
|
||||||
var query memorystore.ApiQuery
|
var query memorystore.APIQuery
|
||||||
if resBody.Queries != nil {
|
if resBody.Queries != nil {
|
||||||
query = resBody.Queries[i]
|
query = resBody.Queries[i]
|
||||||
} else {
|
} else {
|
||||||
@@ -842,8 +842,8 @@ func (ccms *CCMetricStoreInternal) buildNodeQueries(
|
|||||||
metrics []string,
|
metrics []string,
|
||||||
scopes []schema.MetricScope,
|
scopes []schema.MetricScope,
|
||||||
resolution int64,
|
resolution int64,
|
||||||
) ([]memorystore.ApiQuery, []schema.MetricScope, error) {
|
) ([]memorystore.APIQuery, []schema.MetricScope, error) {
|
||||||
queries := make([]memorystore.ApiQuery, 0, len(metrics)*len(scopes)*len(nodes))
|
queries := make([]memorystore.APIQuery, 0, len(metrics)*len(scopes)*len(nodes))
|
||||||
assignedScope := []schema.MetricScope{}
|
assignedScope := []schema.MetricScope{}
|
||||||
|
|
||||||
// Get Topol before loop if subCluster given
|
// Get Topol before loop if subCluster given
|
||||||
@@ -926,7 +926,7 @@ func (ccms *CCMetricStoreInternal) buildNodeQueries(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
queries = append(queries, memorystore.ApiQuery{
|
queries = append(queries, memorystore.APIQuery{
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
Hostname: hostname,
|
Hostname: hostname,
|
||||||
Aggregate: false,
|
Aggregate: false,
|
||||||
@@ -944,7 +944,7 @@ func (ccms *CCMetricStoreInternal) buildNodeQueries(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
queries = append(queries, memorystore.ApiQuery{
|
queries = append(queries, memorystore.APIQuery{
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
Hostname: hostname,
|
Hostname: hostname,
|
||||||
Aggregate: true,
|
Aggregate: true,
|
||||||
@@ -958,7 +958,7 @@ func (ccms *CCMetricStoreInternal) buildNodeQueries(
|
|||||||
|
|
||||||
// HWThread -> HWThead
|
// HWThread -> HWThead
|
||||||
if nativeScope == schema.MetricScopeHWThread && scope == schema.MetricScopeHWThread {
|
if nativeScope == schema.MetricScopeHWThread && scope == schema.MetricScopeHWThread {
|
||||||
queries = append(queries, memorystore.ApiQuery{
|
queries = append(queries, memorystore.APIQuery{
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
Hostname: hostname,
|
Hostname: hostname,
|
||||||
Aggregate: false,
|
Aggregate: false,
|
||||||
@@ -974,7 +974,7 @@ func (ccms *CCMetricStoreInternal) buildNodeQueries(
|
|||||||
if nativeScope == schema.MetricScopeHWThread && scope == schema.MetricScopeCore {
|
if nativeScope == schema.MetricScopeHWThread && scope == schema.MetricScopeCore {
|
||||||
cores, _ := topology.GetCoresFromHWThreads(topology.Node)
|
cores, _ := topology.GetCoresFromHWThreads(topology.Node)
|
||||||
for _, core := range cores {
|
for _, core := range cores {
|
||||||
queries = append(queries, memorystore.ApiQuery{
|
queries = append(queries, memorystore.APIQuery{
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
Hostname: hostname,
|
Hostname: hostname,
|
||||||
Aggregate: true,
|
Aggregate: true,
|
||||||
@@ -991,7 +991,7 @@ func (ccms *CCMetricStoreInternal) buildNodeQueries(
|
|||||||
if nativeScope == schema.MetricScopeHWThread && scope == schema.MetricScopeSocket {
|
if nativeScope == schema.MetricScopeHWThread && scope == schema.MetricScopeSocket {
|
||||||
sockets, _ := topology.GetSocketsFromHWThreads(topology.Node)
|
sockets, _ := topology.GetSocketsFromHWThreads(topology.Node)
|
||||||
for _, socket := range sockets {
|
for _, socket := range sockets {
|
||||||
queries = append(queries, memorystore.ApiQuery{
|
queries = append(queries, memorystore.APIQuery{
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
Hostname: hostname,
|
Hostname: hostname,
|
||||||
Aggregate: true,
|
Aggregate: true,
|
||||||
@@ -1006,7 +1006,7 @@ func (ccms *CCMetricStoreInternal) buildNodeQueries(
|
|||||||
|
|
||||||
// HWThread -> Node
|
// HWThread -> Node
|
||||||
if nativeScope == schema.MetricScopeHWThread && scope == schema.MetricScopeNode {
|
if nativeScope == schema.MetricScopeHWThread && scope == schema.MetricScopeNode {
|
||||||
queries = append(queries, memorystore.ApiQuery{
|
queries = append(queries, memorystore.APIQuery{
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
Hostname: hostname,
|
Hostname: hostname,
|
||||||
Aggregate: true,
|
Aggregate: true,
|
||||||
@@ -1021,7 +1021,7 @@ func (ccms *CCMetricStoreInternal) buildNodeQueries(
|
|||||||
// Core -> Core
|
// Core -> Core
|
||||||
if nativeScope == schema.MetricScopeCore && scope == schema.MetricScopeCore {
|
if nativeScope == schema.MetricScopeCore && scope == schema.MetricScopeCore {
|
||||||
cores, _ := topology.GetCoresFromHWThreads(topology.Node)
|
cores, _ := topology.GetCoresFromHWThreads(topology.Node)
|
||||||
queries = append(queries, memorystore.ApiQuery{
|
queries = append(queries, memorystore.APIQuery{
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
Hostname: hostname,
|
Hostname: hostname,
|
||||||
Aggregate: false,
|
Aggregate: false,
|
||||||
@@ -1037,7 +1037,7 @@ func (ccms *CCMetricStoreInternal) buildNodeQueries(
|
|||||||
if nativeScope == schema.MetricScopeCore && scope == schema.MetricScopeSocket {
|
if nativeScope == schema.MetricScopeCore && scope == schema.MetricScopeSocket {
|
||||||
sockets, _ := topology.GetSocketsFromCores(topology.Node)
|
sockets, _ := topology.GetSocketsFromCores(topology.Node)
|
||||||
for _, socket := range sockets {
|
for _, socket := range sockets {
|
||||||
queries = append(queries, memorystore.ApiQuery{
|
queries = append(queries, memorystore.APIQuery{
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
Hostname: hostname,
|
Hostname: hostname,
|
||||||
Aggregate: true,
|
Aggregate: true,
|
||||||
@@ -1053,7 +1053,7 @@ func (ccms *CCMetricStoreInternal) buildNodeQueries(
|
|||||||
// Core -> Node
|
// Core -> Node
|
||||||
if nativeScope == schema.MetricScopeCore && scope == schema.MetricScopeNode {
|
if nativeScope == schema.MetricScopeCore && scope == schema.MetricScopeNode {
|
||||||
cores, _ := topology.GetCoresFromHWThreads(topology.Node)
|
cores, _ := topology.GetCoresFromHWThreads(topology.Node)
|
||||||
queries = append(queries, memorystore.ApiQuery{
|
queries = append(queries, memorystore.APIQuery{
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
Hostname: hostname,
|
Hostname: hostname,
|
||||||
Aggregate: true,
|
Aggregate: true,
|
||||||
@@ -1068,7 +1068,7 @@ func (ccms *CCMetricStoreInternal) buildNodeQueries(
|
|||||||
// MemoryDomain -> MemoryDomain
|
// MemoryDomain -> MemoryDomain
|
||||||
if nativeScope == schema.MetricScopeMemoryDomain && scope == schema.MetricScopeMemoryDomain {
|
if nativeScope == schema.MetricScopeMemoryDomain && scope == schema.MetricScopeMemoryDomain {
|
||||||
sockets, _ := topology.GetMemoryDomainsFromHWThreads(topology.Node)
|
sockets, _ := topology.GetMemoryDomainsFromHWThreads(topology.Node)
|
||||||
queries = append(queries, memorystore.ApiQuery{
|
queries = append(queries, memorystore.APIQuery{
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
Hostname: hostname,
|
Hostname: hostname,
|
||||||
Aggregate: false,
|
Aggregate: false,
|
||||||
@@ -1083,7 +1083,7 @@ func (ccms *CCMetricStoreInternal) buildNodeQueries(
|
|||||||
// MemoryDoman -> Node
|
// MemoryDoman -> Node
|
||||||
if nativeScope == schema.MetricScopeMemoryDomain && scope == schema.MetricScopeNode {
|
if nativeScope == schema.MetricScopeMemoryDomain && scope == schema.MetricScopeNode {
|
||||||
sockets, _ := topology.GetMemoryDomainsFromHWThreads(topology.Node)
|
sockets, _ := topology.GetMemoryDomainsFromHWThreads(topology.Node)
|
||||||
queries = append(queries, memorystore.ApiQuery{
|
queries = append(queries, memorystore.APIQuery{
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
Hostname: hostname,
|
Hostname: hostname,
|
||||||
Aggregate: true,
|
Aggregate: true,
|
||||||
@@ -1098,7 +1098,7 @@ func (ccms *CCMetricStoreInternal) buildNodeQueries(
|
|||||||
// Socket -> Socket
|
// Socket -> Socket
|
||||||
if nativeScope == schema.MetricScopeSocket && scope == schema.MetricScopeSocket {
|
if nativeScope == schema.MetricScopeSocket && scope == schema.MetricScopeSocket {
|
||||||
sockets, _ := topology.GetSocketsFromHWThreads(topology.Node)
|
sockets, _ := topology.GetSocketsFromHWThreads(topology.Node)
|
||||||
queries = append(queries, memorystore.ApiQuery{
|
queries = append(queries, memorystore.APIQuery{
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
Hostname: hostname,
|
Hostname: hostname,
|
||||||
Aggregate: false,
|
Aggregate: false,
|
||||||
@@ -1113,7 +1113,7 @@ func (ccms *CCMetricStoreInternal) buildNodeQueries(
|
|||||||
// Socket -> Node
|
// Socket -> Node
|
||||||
if nativeScope == schema.MetricScopeSocket && scope == schema.MetricScopeNode {
|
if nativeScope == schema.MetricScopeSocket && scope == schema.MetricScopeNode {
|
||||||
sockets, _ := topology.GetSocketsFromHWThreads(topology.Node)
|
sockets, _ := topology.GetSocketsFromHWThreads(topology.Node)
|
||||||
queries = append(queries, memorystore.ApiQuery{
|
queries = append(queries, memorystore.APIQuery{
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
Hostname: hostname,
|
Hostname: hostname,
|
||||||
Aggregate: true,
|
Aggregate: true,
|
||||||
@@ -1127,7 +1127,7 @@ func (ccms *CCMetricStoreInternal) buildNodeQueries(
|
|||||||
|
|
||||||
// Node -> Node
|
// Node -> Node
|
||||||
if nativeScope == schema.MetricScopeNode && scope == schema.MetricScopeNode {
|
if nativeScope == schema.MetricScopeNode && scope == schema.MetricScopeNode {
|
||||||
queries = append(queries, memorystore.ApiQuery{
|
queries = append(queries, memorystore.APIQuery{
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
Hostname: hostname,
|
Hostname: hostname,
|
||||||
Resolution: resolution,
|
Resolution: resolution,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// All rights reserved. This file is part of cc-backend.
|
// All rights reserved. This file is part of cc-backend.
|
||||||
// Use of this source code is governed by a MIT-style
|
// Use of this source code is governed by a MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package metricdata
|
package metricdata
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -12,6 +13,7 @@ import (
|
|||||||
|
|
||||||
"github.com/ClusterCockpit/cc-backend/internal/config"
|
"github.com/ClusterCockpit/cc-backend/internal/config"
|
||||||
"github.com/ClusterCockpit/cc-backend/internal/graph/model"
|
"github.com/ClusterCockpit/cc-backend/internal/graph/model"
|
||||||
|
"github.com/ClusterCockpit/cc-backend/internal/memorystore"
|
||||||
cclog "github.com/ClusterCockpit/cc-lib/ccLogger"
|
cclog "github.com/ClusterCockpit/cc-lib/ccLogger"
|
||||||
"github.com/ClusterCockpit/cc-lib/schema"
|
"github.com/ClusterCockpit/cc-lib/schema"
|
||||||
)
|
)
|
||||||
@@ -56,7 +58,7 @@ func Init() error {
|
|||||||
mdr = &CCMetricStore{}
|
mdr = &CCMetricStore{}
|
||||||
case "cc-metric-store-internal":
|
case "cc-metric-store-internal":
|
||||||
mdr = &CCMetricStoreInternal{}
|
mdr = &CCMetricStoreInternal{}
|
||||||
config.InternalCCMSFlag = true
|
memorystore.InternalCCMSFlag = true
|
||||||
case "prometheus":
|
case "prometheus":
|
||||||
mdr = &PrometheusDataRepository{}
|
mdr = &PrometheusDataRepository{}
|
||||||
case "test":
|
case "test":
|
||||||
|
|||||||
Reference in New Issue
Block a user