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
2 changes: 1 addition & 1 deletion grafana/rmf-app/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ services:
GF_LOG_FILTERS: plugin.ibm-rmf-datasource:debug
GF_LOG_LEVEL: info
GF_DATAPROXY_LOGGING: 1
GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS: ibm-rmf-app,ibm-rmf-datasource,ibm-rmf-panel
GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS: ibm-rmf-app,ibm-rmf-datasource
8 changes: 6 additions & 2 deletions grafana/rmf-app/grammar/RMFQuery.g4
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Examples:
grammar RMFQuery;


query: WS* RES_TYPE (DOT REPORT)? DOT identifier WS* qualifiers? WS* EOF;
query: WS* RES_TYPE ((DOT REPORT) | (DOT REPORT_CAPTION) | (DOT REPORT_BANNER))? 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.
Expand All @@ -54,14 +54,16 @@ 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
: IDENTIFIER | RES_TYPE | REPORT | REPORT_CAPTION | REPORT_BANNER | WORKSCOPE | RANGE | ULQ | NAME | FILTER
| PAT | LB | UB | HI | LO | ORD | ORD_OPTION | INTEGER | STRING_UNQUOTED;
stringSpaced: stringUnquoted (WS + stringUnquoted)*;
stringDotted: stringUnquoted (DOT stringUnquoted)*;
string: stringDotted | STRING_QUOTED;


REPORT: R E P O R T;
REPORT_CAPTION: R E P O R T UNDERSCORE C A P T I O N;
REPORT_BANNER: R E P O R T UNDERSCORE B A N N E R;
WORKSCOPE: W O R K S C O P E;
RANGE: R A N G E;
ULQ: U L Q;
Expand Down Expand Up @@ -144,9 +146,11 @@ WS: [ \n\t\r]+;

fragment SINGLE_QUOTE: '\'';
fragment DOUBLE_QOUTE: '"';
fragment UNDERSCORE: '_';
fragment STRING_ITEM_NO_QUOTE: ~[ .;{}=,];
fragment STRING_ITEM_SINGLE_QUOTE: ~'\'';
fragment STRING_ITEM_DOUBLE_QUOTE: ~'"';
fragment STRING_ITEM_UNDERSCORE: ~'_';
fragment A : [aA];
fragment B : [bB];
fragment C : [cC];
Expand Down
2 changes: 1 addition & 1 deletion grafana/rmf-app/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"author": "IBM",
"name": "ibm-rmf",
"version": "1.1.1",
"version": "2.0.0",
"description": "IBM RMF for z/OS",
"license": "Apache-2.0",
"scripts": {
Expand Down
35 changes: 32 additions & 3 deletions grafana/rmf-app/pkg/plugin/datasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"encoding/json"
"errors"
"net/http"
"regexp"
"runtime/debug"
"slices"
"strings"
Expand Down Expand Up @@ -56,6 +57,7 @@ var (
const ChannelCacheSizeMB = 64
const SdsDelay = 5 * time.Second
const TimeSeriesType = "TimeSeries"
const QueryPattern = `^([A-Za-z_][A-Za-z0-9_]*)\(([^)]*)\)$` // e.g., banner(resource), table(resource), caption(resource)

type RMFDatasource struct {
uid string
Expand All @@ -65,6 +67,7 @@ type RMFDatasource struct {
ddsClient *dds.Client
single singleflight.Group
omegamonDs string
queryMatcher *regexp.Regexp
}

// NewRMFDatasource creates a new instance of the RMF datasource.
Expand All @@ -86,6 +89,7 @@ func NewRMFDatasource(ctx context.Context, settings backend.DataSourceInstanceSe
"uid", settings.UID, "name", settings.Name,
"url", config.URL, "timeout", config.Timeout, "cacheSize", config.CacheSize,
"username", config.Username, "tlsSkipVerify", config.JSON.TlsSkipVerify)
ds.queryMatcher = regexp.MustCompile(QueryPattern)
return ds, nil
}

Expand Down Expand Up @@ -335,10 +339,14 @@ func (ds *RMFDatasource) QueryData(ctx context.Context, req *backend.QueryDataRe
}
} else {
// Query non-timeseries data
r := dds.NewRequest(params.Resource.Value, q.TimeRange.From.UTC(), q.TimeRange.To.UTC(), mintime)
queryKind, query := ds.parseQuery(params.Resource.Value)
r := dds.NewRequest(query, 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 {
newFrame := ds.getCachedReportFrames(r)
if newFrame == nil {
newFrame, err = ds.getFrame(r, false)
}
if err != nil {
var msg *dds.Message
if errors.As(err, &msg) {
response.Error = err
Expand All @@ -348,6 +356,19 @@ func (ds *RMFDatasource) QueryData(ctx context.Context, req *backend.QueryDataRe
response.Status = backend.StatusInternal
}
} else if newFrame != nil {
ds.setCachedReportFrames(newFrame, r)
switch queryKind {
case "banner":
newFrame = frame.GetFrameBanner(newFrame)
case "caption":
newFrame = frame.GetFrameCaption(newFrame)
case "table":
newFrame = frame.GetFrameTable(newFrame)
default:
if strings.Contains(query, "report=") {
newFrame = frame.GetFrameTable(newFrame)
}
}
response.Frames = append(response.Frames, newFrame)
}
}
Expand Down Expand Up @@ -450,3 +471,11 @@ func (ds *RMFDatasource) SubscribeStream(_ context.Context, req *backend.Subscri
func (d *RMFDatasource) PublishStream(_ context.Context, req *backend.PublishStreamRequest) (*backend.PublishStreamResponse, error) {
return &backend.PublishStreamResponse{Status: backend.PublishStreamStatusPermissionDenied}, nil
}

func (d *RMFDatasource) parseQuery(resource string) (string, string) {
matches := d.queryMatcher.FindStringSubmatch(resource)
if len(matches) == 3 {
return strings.ToLower(matches[1]), matches[2]
}
return "", resource
}
83 changes: 83 additions & 0 deletions grafana/rmf-app/pkg/plugin/frame/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package frame

import (
"errors"
"fmt"
"slices"
"strings"
"time"

Expand Down Expand Up @@ -117,3 +119,84 @@ func MergeInto(dst *data.Frame, src *data.Frame) (*data.Frame, error) {
}
return dst, nil
}

func CopyReportField(field *data.Field, length int) *data.Field {
var newField *data.Field
t := field.Type()
switch t {
case data.FieldTypeNullableFloat64:
newField = data.NewField(field.Name, field.Labels, []*float64{})
case data.FieldTypeNullableString:
newField = data.NewField(field.Name, field.Labels, []*string{})
default:
newField = data.NewField(field.Name, field.Labels, []string{})
}
newField.SetConfig(field.Config)
length = slices.Min([]int{length, field.Len()})
for i := 0; i < length; i++ {
newField.Append(field.At(i))
}
return newField
}

func GetFrameTable(f *data.Frame) *data.Frame {
var newFrame data.Frame
for _, field := range f.Fields {
if !strings.HasPrefix(field.Name, CaptionPrefix) &&
!strings.HasPrefix(field.Name, BannerPrefix) {
var newField = CopyReportField(field, field.Len())
newField.Delete(0)
newFrame.Fields = append(newFrame.Fields, newField)
}
}
return &newFrame
}

func GetFrameCaption(f *data.Frame) *data.Frame {
var newFrame data.Frame
newFrame.Fields = append(newFrame.Fields, data.NewField("Key", nil, []string{}))
newFrame.Fields = append(newFrame.Fields, data.NewField("Value", nil, []string{}))
for _, field := range f.Fields {
if strings.HasPrefix(field.Name, CaptionPrefix) {
key := strings.TrimPrefix(field.Name, CaptionPrefix)
value := getStringAt(field, 0)
newFrame.Fields[0].Append(key)
newFrame.Fields[1].Append(value)
}
}
return &newFrame
}

func GetFrameBanner(f *data.Frame) *data.Frame {
var newFrame data.Frame
newFrame.Fields = append(newFrame.Fields, data.NewField("Key", nil, []string{}))
newFrame.Fields = append(newFrame.Fields, data.NewField("Value", nil, []string{}))
for _, field := range f.Fields {
if strings.HasPrefix(field.Name, BannerPrefix) {
key := strings.TrimPrefix(field.Name, BannerPrefix)
value := getStringAt(field, 0)
newFrame.Fields[0].Append(key)
newFrame.Fields[1].Append(value)
}
}
return &newFrame
}

func getStringAt(field *data.Field, index int) string {
value := ""
if field.Len() > index {
v := field.At(index)
if v != nil {
if s, ok := v.(string); ok {
value = s
} else if s, ok := v.(*string); ok {
if s != nil {
value = *s
}
} else if f, ok := v.(float64); ok {
value = fmt.Sprintf("%f", f)
}
}
}
return value
}
11 changes: 11 additions & 0 deletions grafana/rmf-app/pkg/plugin/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,17 @@ func (ds *RMFDatasource) getCachedTSFrames(r *dds.Request, stop time.Time, step
return f, jump, err
}

func (ds *RMFDatasource) getCachedReportFrames(r *dds.Request) *data.Frame {
return ds.frameCache.Get(r, true)
}

func (ds *RMFDatasource) setCachedReportFrames(f *data.Frame, r *dds.Request) {
logger := log.Logger.With("func", "setCachedReportFrames")
if err := ds.frameCache.Set(f, r, true); err != nil {
logger.Error("failed to save data in cache", "request", r.String(), "reason", err)
}
}

func (ds *RMFDatasource) serveTSFrame(ctx context.Context, sender *backend.StreamSender, fields frame.SeriesFields, r *dds.Request, hist bool) error {
logger := log.Logger.With("func", "serveTSFrame")
var f *data.Frame
Expand Down
Loading
Loading