Skip to content

Commit

Permalink
HELM-446: Convert Perf datasource NaN values to nulls
Browse files Browse the repository at this point in the history
  • Loading branch information
synqotik committed Jun 29, 2023
1 parent 654652e commit c5d0ffc
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 68 deletions.
133 changes: 73 additions & 60 deletions src/datasources/perf-ds/PerformanceHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { ArrayVector, DataFrame, Field, FieldType } from "@grafana/data"
import { getTemplateSrv } from "@grafana/runtime";
import { FunctionFormatter } from "lib/function_formatter";
import { trimChar, isString } from "lib/utils";
import { OnmsMeasurementsQueryResponse } from "./types";
import { ArrayVector, DataFrame, Field, FieldType } from '@grafana/data'
import { getTemplateSrv } from '@grafana/runtime'
import { FunctionFormatter } from 'lib/function_formatter'
import { trimChar, isString } from 'lib/utils'
import { OnmsMeasurementsQueryMetadata, OnmsMeasurementsQueryResponse, OnmsMeasurementsQueryResponseColumnItem } from './types'

/**
* Get 'windowed' timestamps that are between start/end range.
Expand Down Expand Up @@ -49,67 +49,79 @@ const getWindowedTimestamps = (timestamps: number[], start: number, end: number)
/**
* Convert QueryResponse data returned by OpenNMS Measurements Rest API to Grafana DataFrame format.
*/
export const measurementResponseToDataFrame =
(measurementData: OnmsMeasurementsQueryResponse): DataFrame[] => {

const { start, end, labels, columns, timestamps, metadata } = measurementData

const dataFrames: DataFrame[] = []

let dataFrame: DataFrame = getEmptyDataFrame()

if (!timestamps || !timestamps.length) {
dataFrames.push(dataFrame)
} else {
const { windowedTimestamps, startIndex, endIndex } = getWindowedTimestamps(timestamps, start, end)

// no data or no data within the start/end timespan, return an empty DataFrame
if (windowedTimestamps.length === 0) {
dataFrames.push(dataFrame)
} else {

for (let i = 0; i < labels?.length; i++) {
dataFrame = getEmptyDataFrame()

dataFrame.length = windowedTimestamps.length

dataFrame.fields.push({
name: 'Time',
type: FieldType.time,
config: {},
values: new ArrayVector<number>(windowedTimestamps)
} as Field)

const label = metadata && metadata.resources ?
FunctionFormatter.format(labels[i], metadata) :
labels[i]

if (columns && columns.length) {

const column = columns[i]
const windowedValues = column.values.slice(startIndex, endIndex + 1)
export const measurementResponseToDataFrame = (measurementData: OnmsMeasurementsQueryResponse): DataFrame[] => {
const { start, end, labels, columns, timestamps, metadata } = measurementData
const dataFrames: DataFrame[] = []
let dataFrame: DataFrame = getEmptyDataFrame()

if (!timestamps || !timestamps.length) {
dataFrames.push(dataFrame)
} else {
const { windowedTimestamps, startIndex, endIndex } = getWindowedTimestamps(timestamps, start, end)

// no data or no data within the start/end timespan, return an empty DataFrame
if (windowedTimestamps.length === 0) {
dataFrames.push(dataFrame)
} else {
for (let i = 0; i < labels?.length; i++) {
const column = columns?.length ? columns[i] : null

dataFrame = measurementColumnToDataFrame (windowedTimestamps, startIndex, endIndex, metadata, labels[i], column)
dataFrames.push(dataFrame)
}
}
}

let field = {
name: label || 'Value',
type: FieldType.number, // number but actual data may be a string representing a number or "NaN"
config: {},
values: new ArrayVector<string | number | null>(windowedValues)
} as Field
dataFrame.fields.push(field)
}
dataFrames.push(dataFrame)
}
}
}
return dataFrames
}

return dataFrames
}
/**
* Convert QueryResponse data for one column returned by OpenNMS Measurements Rest API to Grafana DataFrame format.
*/
const measurementColumnToDataFrame = (
windowedTimestamps: number[],
startIndex: number,
endIndex: number,
metadata: OnmsMeasurementsQueryMetadata,
label: string,
column: OnmsMeasurementsQueryResponseColumnItem | null
): DataFrame => {
const dataFrame = getEmptyDataFrame()

dataFrame.length = windowedTimestamps.length

dataFrame.fields.push({
name: 'Time',
type: FieldType.time,
config: {},
values: new ArrayVector<number>(windowedTimestamps)
} as Field)

const formattedLabel = metadata?.resources ? FunctionFormatter.format(label, metadata) : label

if (column) {
// Only use values within the timestamp window
// Replace any literal 'NaN' values with null
let windowedValues = column.values.slice(startIndex, endIndex + 1).map(v => v === 'NaN' ? null : v)

let field = {
name: formattedLabel || 'Value',
type: FieldType.number, // will be a number, a string representing a number or else null
config: {},
values: new ArrayVector<string | number | null>(windowedValues)
} as Field

dataFrame.fields.push(field)
}

return dataFrame
}

const getEmptyDataFrame = () => {
const getEmptyDataFrame = (): DataFrame => {
return {
name: '',
length: 0,
fields: []
fields: [] as Field[]
}
}

Expand All @@ -122,6 +134,7 @@ export const isTemplateVariable = (property: { id?: string, label?: string }) =>
export const getTemplateVariable = (property?: { label?: string } | string) => {
const ts = getTemplateSrv()
let result = ''

if (property) {
if (property.hasOwnProperty('id')) {
result = property['id']
Expand Down
20 changes: 12 additions & 8 deletions src/datasources/perf-ds/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,16 +140,20 @@ export interface OnmsMeasurementsQueryMetadata {
nodes: Array<{ node: OnmsMeasurementsQueryNode }>;
}

export interface OnmsMeasurementsQueryResponseColumnItem {
values: Array<string | number | null> // number, string representing a number, 'NaN' or null
}

// See features/measurements/api, package org.opennms.netmgt.measurements.model.QueryResponse
export interface OnmsMeasurementsQueryResponse {
step: number;
start: number;
end: number;
timestamps: number[];
labels: string[];
columns: Array<{ values: Array<string | number | null> }>; // TODO: Is this returned as string or number in Json???
constants: Array<{ key: string, value: string }>;
metadata: OnmsMeasurementsQueryMetadata;
step: number
start: number
end: number
timestamps: number[]
labels: string[]
columns: OnmsMeasurementsQueryResponseColumnItem[]
constants: Array<{ key: string, value: string }>
metadata: OnmsMeasurementsQueryMetadata
}

export interface QuickSelect {
Expand Down

0 comments on commit c5d0ffc

Please sign in to comment.