Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions grafana/rmf-app/grammar/RMFQuery.g4
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,16 @@ Examples:
grammar RMFQuery;


query: WS* RES_TYPE (DOT REPORT)? DOT identifier WS* qualifiers? WS* EOF;
query: WS* RES_TYPE (DOT (REPORT | REPORTONLY | REPORTBANNER | REPORTCAPTION))? DOT identifier WS* qualifiers? WS* EOF;
// A workaround: some reports are also resource TYPES (e.g. CPC).
// In general, the problem is that we define keywords that are not distiguashable for antlr from
// string literals which we also support.
identifier: stringSpaced;
qualifiers: LBRACE WS* qualifier WS* (COMMA WS* qualifier WS*)* RBRACE;
qualifier: ulq | name | filter | workscope;
qualifier: ulq | name | filter | workscope | frame;
ulq: ULQ EQUAL string;
name: NAME EQUAL string;
frame: FRAME EQUAL string;
filter: FILTER EQUAL filterValue;
filterValue: filterItem (SEMI filterItem)*;
filterItem: pat | lb | ub | hi | lo | ord;
Expand All @@ -54,14 +55,18 @@ workscopeValue: string? COMMA string? COMMA WORKSCOPE_TYPE;
// Another workaround: it won't work on token level.
number: INTEGER | DECIMAL;
stringUnquoted
: IDENTIFIER | RES_TYPE | REPORT | WORKSCOPE | RANGE | ULQ | NAME | FILTER
| PAT | LB | UB | HI | LO | ORD | ORD_OPTION | INTEGER | STRING_UNQUOTED;
: IDENTIFIER | RES_TYPE | REPORT | REPORTONLY | REPORTBANNER | REPORTCAPTION | WORKSCOPE | RANGE | ULQ | NAME | FILTER
| PAT | LB | UB | HI | LO | ORD | ORD_OPTION | INTEGER | STRING_UNQUOTED | FRAME;
stringSpaced: stringUnquoted (WS + stringUnquoted)*;
stringDotted: stringUnquoted (DOT stringUnquoted)*;
string: stringDotted | STRING_QUOTED;


FRAME: F R A M E;
REPORT: R E P O R T;
REPORTONLY: R E P O R T O N L Y;
REPORTBANNER: R E P O R T B A N N E R;
REPORTCAPTION: R E P O R T C A P T I O N;
WORKSCOPE: W O R K S C O P E;
RANGE: R A N G E;
ULQ: U L Q;
Expand Down
13 changes: 7 additions & 6 deletions grafana/rmf-app/pkg/plugin/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,13 @@ type Config struct {
// Custom RMF settings.
CacheSizeRaw string `json:"cacheSize"`
// Legacy custom RMF settings. We should ge rid of these at some point.
Server *string `json:"path"`
Port string `json:"port"`
SSL bool `json:"ssl"`
Username string `json:"userName"`
Password string `json:"password"`
SSLVerify bool `json:"skipVerify"` // NB: the meaning of JSON field is inverted.
Server *string `json:"path"`
Port string `json:"port"`
SSL bool `json:"ssl"`
Username string `json:"userName"`
Password string `json:"password"`
SSLVerify bool `json:"skipVerify"` // NB: the meaning of JSON field is inverted.
OmegamonDs string `json:"omegamonDs"`
}
}

Expand Down
80 changes: 74 additions & 6 deletions grafana/rmf-app/pkg/plugin/datasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"errors"
"net/http"
"runtime/debug"
"slices"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -63,6 +64,7 @@ type RMFDatasource struct {
frameCache *cache.FrameCache
ddsClient *dds.Client
single singleflight.Group
omegamonDs string
}

// NewRMFDatasource creates a new instance of the RMF datasource.
Expand All @@ -79,6 +81,7 @@ func NewRMFDatasource(ctx context.Context, settings backend.DataSourceInstanceSe
config.JSON.TlsSkipVerify, config.JSON.DisableCompression)
ds.channelCache = cache.NewChannelCache(ChannelCacheSizeMB)
ds.frameCache = cache.NewFrameCache(config.CacheSize)
ds.omegamonDs = config.JSON.OmegamonDs
logger.Info("initialized a datasource",
"uid", settings.UID, "name", settings.Name,
"url", config.URL, "timeout", config.Timeout, "cacheSize", config.CacheSize,
Expand Down Expand Up @@ -148,13 +151,28 @@ func (ds *RMFDatasource) CallResource(ctx context.Context, req *backend.CallReso
switch req.Path {
// FIXME: it's a contained.xml request for M3 resource tree. Re-factor accordingly.
case "variablequery":
// Extract the query parameter from the POST request
jsonStr := string(req.Body)
varRequest := VariableQueryRequest{}
err := json.Unmarshal([]byte(jsonStr), &varRequest)
err := json.Unmarshal(req.Body, &varRequest)
if err != nil {
return log.ErrorWithId(logger, log.InternalError, "could not unmarshal data", "error", err)
}
q := varRequest.Query
q = strings.Trim(q, " ")
q = strings.ToLower(q)

switch q {
case "sysplex":
data, _ := ds.sysplexContainedJson()
return sender.Send(&backend.CallResourceResponse{Status: http.StatusOK, Body: data})
case "systems":
data, _ := ds.systemsContainedJson()
return sender.Send(&backend.CallResourceResponse{Status: http.StatusOK, Body: data})
case "omegamonds":
data, _ := ds.omegamonContainedJson()
return sender.Send(&backend.CallResourceResponse{Status: http.StatusOK, Body: data})
}

// Extract the query parameter from the POST request
ddsResource := varRequest.Query
if len(strings.TrimSpace(ddsResource)) == 0 {
return log.ErrorWithId(logger, log.InputError, "variable query cannot be blank")
Expand Down Expand Up @@ -184,6 +202,50 @@ func (ds *RMFDatasource) CallResource(ctx context.Context, req *backend.CallReso
}
}

func (ds *RMFDatasource) sysplexContainedJson() ([]byte, error) {
sysplex := ds.ddsClient.GetSysplex()
return toContainedJson([]string{sysplex})
}

func (ds *RMFDatasource) systemsContainedJson() ([]byte, error) {
systems := ds.ddsClient.GetSystems()
slices.Sort(systems)
return toContainedJson(systems)
}

func (ds *RMFDatasource) omegamonContainedJson() ([]byte, error) {
return toContainedJson([]string{ds.omegamonDs})
}

func toContainedJson(resources []string) ([]byte, error) {
type Resource struct {
Reslabel string `json:"reslabel"`
}
type Contained struct {
Resource []Resource `json:"resource"`
}
type ContainedResource struct {
Contained Contained `json:"contained"`
}
type Ddsml struct {
ContainedResourcesList []ContainedResource `json:"containedResourcesList"`
}

contained := Contained{Resource: []Resource{}}
for _, res := range resources {
contained.Resource = append(contained.Resource, Resource{Reslabel: "," + res + ","})
}

var containedResource = ContainedResource{
Contained: contained,
}

result := Ddsml{
ContainedResourcesList: []ContainedResource{containedResource},
}
return json.Marshal(result)
}

type RequestParams struct {
Resource struct {
Value string `json:"value"`
Expand Down Expand Up @@ -276,7 +338,7 @@ func (ds *RMFDatasource) QueryData(ctx context.Context, req *backend.QueryDataRe
r := dds.NewRequest(params.Resource.Value, q.TimeRange.From.UTC(), q.TimeRange.To.UTC(), mintime)
response = &backend.DataResponse{}
// FIXME: doesn't it need to be cached?
if newFrame, err := ds.getFrame(r, false); err != nil {
if fms, err := ds.getFrame(r, false); err != nil {
var msg *dds.Message
if errors.As(err, &msg) {
response.Error = err
Expand All @@ -285,8 +347,14 @@ func (ds *RMFDatasource) QueryData(ctx context.Context, req *backend.QueryDataRe
response.Error = log.FrameErrorWithId(logger, err)
response.Status = backend.StatusInternal
}
} else if newFrame != nil {
response.Frames = append(response.Frames, newFrame)
} else if fms != nil {
if r.Frame < 0 {
for _, f := range fms {
response.Frames = append(response.Frames, f)
}
} else {
response.Frames = append(response.Frames, fms[r.Frame])
}
}
}
}
Expand Down
24 changes: 23 additions & 1 deletion grafana/rmf-app/pkg/plugin/dds/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ type Client struct {
httpClient *http.Client
headerMap *HeaderMap
timeData *TimeData
resource *Resource
systems []string
useXmlExt atomic.Bool

stopChan chan struct{}
Expand Down Expand Up @@ -247,7 +249,7 @@ func (c *Client) ensureTimeData() *TimeData {
func (c *Client) updateTimeData() *TimeData {
logger := log.Logger.With("func", "updateTimeData")
result, _, _ := c.single.Do("timeData", func() (any, error) {
response, err := c.Get(PerformPath, "resource", ",,SYSPLEX", "id", "8D0D50")
response, err := c.Get(PerformPath, "resource", ",,SYSPLEX", "id", "8D0D60")
if err != nil {
logger.Error("unable to fetch DDS time data", "error", err)
return nil, err
Expand All @@ -257,8 +259,15 @@ func (c *Client) updateTimeData() *TimeData {
logger.Error("unable to fetch DDS time data", "error", "no time data in DDS response")
return nil, err
}
resource := response.Reports[0].Resource
if resource == nil {
logger.Error("unable to fetch DDS resource", "error", "no resource data in DDS response")
}
systems := response.Reports[0].GetRowNames()
c.rwMutex.Lock()
c.timeData = timeData
c.resource = resource
c.systems = systems
c.rwMutex.Unlock()
logger.Debug("DDS time data updated")
return timeData, nil
Expand All @@ -277,3 +286,16 @@ func (c *Client) GetCachedMintime() time.Duration {
}
return time.Duration(minTime) * time.Second
}

func (c *Client) GetSysplex() string {
c.ensureTimeData()
if c.resource != nil {
return c.resource.GetName()
}
return ""
}

func (c *Client) GetSystems() []string {
c.ensureTimeData()
return c.systems
}
9 changes: 9 additions & 0 deletions grafana/rmf-app/pkg/plugin/dds/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package dds
import (
"fmt"
"net/url"
"strconv"
"strings"
"time"

Expand All @@ -29,11 +30,19 @@ import (
type Request struct {
Resource string
TimeRange data.TimeRange
Frame int
}

func NewRequest(res string, from time.Time, to time.Time, step time.Duration) *Request {
frame := -1
i := strings.Index(res, "&frame=")
if i > 0 {
frame, _ = strconv.Atoi(res[i+7 : i+8])
res = res[:i]
}
q := Request{Resource: res, TimeRange: data.TimeRange{From: from, To: to}}
q.Align(step)
q.Frame = frame
return &q
}

Expand Down
25 changes: 25 additions & 0 deletions grafana/rmf-app/pkg/plugin/dds/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package dds

import (
"fmt"
"strings"
"time"
)

Expand Down Expand Up @@ -55,12 +56,23 @@ type Report struct {
Metric *Metric
Message *Message
Caption Caption
Resource *Resource `json:"resource"`
Headers struct {
Cols []Col `json:"col"`
} `json:"columnHeaders"`
Rows []Row `json:"row"`
}

func (r Report) GetRowNames() []string {
var names []string
if r.Rows != nil {
for _, row := range r.Rows {
names = append(names, row.Cols[0])
}
}
return names
}

type TimeData struct {
// FIXME: don't use these in report headers: they are in DDS timezone. Remove from the mapping.
LocalStart DateTime `json:"localStart"`
Expand All @@ -81,6 +93,19 @@ type TimeData struct {
} `json:"dataRange"`
}

type Resource struct {
Reslabel string `json:"reslabel"`
Restype string `json:"restype"`
}

func (r Resource) GetName() string {
ss := strings.Split(r.Reslabel, ",")
if len(ss) > 1 {
return ss[1]
}
return r.Reslabel
}

type DateTime struct {
time.Time
}
Expand Down
Loading
Loading