mirror of https://github.com/grafana/grafana.git
				
				
				
			
		
			
	
	
		
			315 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			Go
		
	
	
	
		
		
			
		
	
	
			315 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			Go
		
	
	
	
|  | package deprecated | ||
|  | 
 | ||
|  | import ( | ||
|  | 	"encoding/json" | ||
|  | 	"fmt" | ||
|  | 	"time" | ||
|  | 
 | ||
|  | 	"github.com/grafana/grafana-plugin-sdk-go/data" | ||
|  | ) | ||
|  | 
 | ||
|  | // InsightsMetricsResultToFrame converts a MetricsResult (an Application Insights metrics query response) to a dataframe.
 | ||
|  | // Due to the dynamic nature of the MetricsResult object, the name of the metric, aggregation,
 | ||
|  | // and requested dimensions are used to determine the expected shape of the object.
 | ||
|  | // This builds all series into a single data.Frame with one time index (a wide formatted time series frame).
 | ||
|  | func InsightsMetricsResultToFrame(mr MetricsResult, metric, agg string, dimensions []string) (*data.Frame, error) { | ||
|  | 	dimLen := len(dimensions) | ||
|  | 
 | ||
|  | 	// The Response has both Start and End times, so we name the column "StartTime".
 | ||
|  | 	frame := data.NewFrame("", data.NewField("StartTime", nil, []time.Time{})) | ||
|  | 
 | ||
|  | 	fieldIdxMap := map[string]int{} // a map of a string representation of the labels to the Field index in the frame.
 | ||
|  | 
 | ||
|  | 	rowCounter := 0 // row in the resulting frame
 | ||
|  | 
 | ||
|  | 	if mr.Value == nil { // never seen this response, but to ensure there is no panic
 | ||
|  | 		return nil, fmt.Errorf("unexpected nil response or response value in metrics result") | ||
|  | 	} | ||
|  | 
 | ||
|  | 	for _, seg := range *mr.Value.Segments { // each top level segment in the response shares timestamps.
 | ||
|  | 		frame.Extend(1) | ||
|  | 		frame.Set(0, rowCounter, seg.Start) // field 0 is the time field
 | ||
|  | 		labels := data.Labels{} | ||
|  | 
 | ||
|  | 		// handleLeafSegment is for the leaf MetricsSegmentInfo nodes in the response.
 | ||
|  | 		// A leaf node contains an aggregated value, and when there are multiple dimensions, a label key/value pair.
 | ||
|  | 		handleLeafSegment := func(s MetricsSegmentInfo) error { | ||
|  | 			// since this is a dynamic response, everything we are interested in here from JSON
 | ||
|  | 			// is Marshalled (mapped) into the AdditionalProperties property.
 | ||
|  | 			v, err := valFromLeafAP(s.AdditionalProperties, metric, agg) | ||
|  | 			if err != nil { | ||
|  | 				return err | ||
|  | 			} | ||
|  | 
 | ||
|  | 			if dimLen != 0 { // when there are dimensions, the final dimension is in this inner segment.
 | ||
|  | 				dimension := dimensions[dimLen-1] | ||
|  | 				dimVal, err := dimValueFromAP(s.AdditionalProperties, dimension) | ||
|  | 				if err != nil { | ||
|  | 					return err | ||
|  | 				} | ||
|  | 				labels[dimension] = dimVal | ||
|  | 			} | ||
|  | 
 | ||
|  | 			if _, ok := fieldIdxMap[labels.String()]; !ok { | ||
|  | 				// When we find a new combination of labels for the metric, a new Field is appended.
 | ||
|  | 				frame.Fields = append(frame.Fields, data.NewField(metric, labels.Copy(), make([]*float64, rowCounter+1))) | ||
|  | 				fieldIdxMap[labels.String()] = len(frame.Fields) - 1 | ||
|  | 			} | ||
|  | 
 | ||
|  | 			frame.Set(fieldIdxMap[labels.String()], rowCounter, v) | ||
|  | 
 | ||
|  | 			return nil | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// Simple case with no segments/dimensions
 | ||
|  | 		if dimLen == 0 { | ||
|  | 			if err := handleLeafSegment(seg); err != nil { | ||
|  | 				return nil, err | ||
|  | 			} | ||
|  | 			rowCounter++ | ||
|  | 			continue | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// Multiple dimension case
 | ||
|  | 		var traverse func(segments *[]MetricsSegmentInfo, depth int) error | ||
|  | 
 | ||
|  | 		// traverse walks segments collecting dimensions into labels until leaf segments are
 | ||
|  | 		// reached, and then handleInnerSegment is called. The final k/v label pair is
 | ||
|  | 		// in the leaf segment.
 | ||
|  | 		// A non-recursive implementation would probably be better.
 | ||
|  | 		traverse = func(segments *[]MetricsSegmentInfo, depth int) error { | ||
|  | 			if segments == nil { | ||
|  | 				return nil | ||
|  | 			} | ||
|  | 			for _, seg := range *segments { | ||
|  | 				if seg.Segments == nil { | ||
|  | 					if err := handleLeafSegment(seg); err != nil { | ||
|  | 						return err | ||
|  | 					} | ||
|  | 					continue | ||
|  | 				} | ||
|  | 				dimension := dimensions[depth] | ||
|  | 				dimVal, err := dimValueFromAP(seg.AdditionalProperties, dimension) | ||
|  | 				if err != nil { | ||
|  | 					return err | ||
|  | 				} | ||
|  | 				labels[dimension] = dimVal | ||
|  | 				if err := traverse(seg.Segments, depth+1); err != nil { | ||
|  | 					return err | ||
|  | 				} | ||
|  | 			} | ||
|  | 			return nil | ||
|  | 		} | ||
|  | 
 | ||
|  | 		if err := traverse(seg.Segments, 0); err != nil { | ||
|  | 			return nil, err | ||
|  | 		} | ||
|  | 		rowCounter++ | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if len(frame.Fields) == 1 { // No data, only a time column, no sort
 | ||
|  | 		return frame, nil | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if err := data.SortWideFrameFields(frame, dimensions...); err != nil { | ||
|  | 		return nil, err | ||
|  | 	} | ||
|  | 
 | ||
|  | 	return frame, nil | ||
|  | } | ||
|  | 
 | ||
|  | // valFromLeafAP extracts value for the given metric and aggregation (agg)
 | ||
|  | // from the dynamic AdditionalProperties properties of a leaf node. It is for use in the InsightsMetricsResultToFrame
 | ||
|  | // function.
 | ||
|  | func valFromLeafAP(ap map[string]interface{}, metric, agg string) (*float64, error) { | ||
|  | 	if ap == nil { | ||
|  | 		return nil, fmt.Errorf("expected additional properties for metric %v not found in leaf segment", metric) | ||
|  | 	} | ||
|  | 	met, ok := ap[metric] | ||
|  | 	if !ok { | ||
|  | 		return nil, fmt.Errorf("expected additional properties for metric %v not found in leaf segment", metric) | ||
|  | 	} | ||
|  | 
 | ||
|  | 	metMap, ok := met.(map[string]interface{}) | ||
|  | 	if !ok { | ||
|  | 		return nil, fmt.Errorf("unexpected type for additional properties not found in leaf segment, want map[string]interface{}, but got %T", met) | ||
|  | 	} | ||
|  | 	metVal, ok := metMap[agg] | ||
|  | 	if !ok { | ||
|  | 		return nil, fmt.Errorf("expected value for aggregation %v not found in leaf segment", agg) | ||
|  | 	} | ||
|  | 	var v *float64 | ||
|  | 	if val, ok := metVal.(float64); ok { | ||
|  | 		v = &val | ||
|  | 	} | ||
|  | 
 | ||
|  | 	return v, nil | ||
|  | } | ||
|  | 
 | ||
|  | // dimValueFromAP fetches the value as a string for the corresponding dimension from the dynamic AdditionalProperties properties of a leaf node. It is for use in the InsightsMetricsResultToFrame
 | ||
|  | // function.
 | ||
|  | func dimValueFromAP(ap map[string]interface{}, dimension string) (string, error) { | ||
|  | 	rawDimValue, ok := ap[dimension] | ||
|  | 	if !ok { | ||
|  | 		return "", fmt.Errorf("expected dimension key %v not found in response", dimension) | ||
|  | 	} | ||
|  | 	dimValue, ok := rawDimValue.(string) | ||
|  | 	if !ok { | ||
|  | 		return "", fmt.Errorf("unexpected non-string value for the value for dimension %v, got type %T with a value of %v", dimension, rawDimValue, dimValue) | ||
|  | 	} | ||
|  | 	return dimValue, nil | ||
|  | } | ||
|  | 
 | ||
|  | // MetricsResult a metric result.
 | ||
|  | // This is copied from azure-sdk-for-go/services/preview/appinsights/v1/insights.
 | ||
|  | type MetricsResult struct { | ||
|  | 	Value *MetricsResultInfo `json:"value,omitempty"` | ||
|  | } | ||
|  | 
 | ||
|  | // MetricsResultInfo a metric result data.
 | ||
|  | // This is copied from azure-sdk-for-go/services/preview/appinsights/v1/insights (except time Type is changed).
 | ||
|  | type MetricsResultInfo struct { | ||
|  | 	// AdditionalProperties - Unmatched properties from the message are deserialized this collection
 | ||
|  | 	AdditionalProperties map[string]interface{} `json:""` | ||
|  | 	// Start - Start time of the metric.
 | ||
|  | 	Start time.Time `json:"start,omitempty"` | ||
|  | 	// End - Start time of the metric.
 | ||
|  | 	End time.Time `json:"end,omitempty"` | ||
|  | 	// Interval - The interval used to segment the metric data.
 | ||
|  | 	Interval *string `json:"interval,omitempty"` | ||
|  | 	// Segments - Segmented metric data (if segmented).
 | ||
|  | 	Segments *[]MetricsSegmentInfo `json:"segments,omitempty"` | ||
|  | } | ||
|  | 
 | ||
|  | // MetricsSegmentInfo is a metric segment.
 | ||
|  | // This is copied from azure-sdk-for-go/services/preview/appinsights/v1/insights (except time Type is changed).
 | ||
|  | type MetricsSegmentInfo struct { | ||
|  | 	// AdditionalProperties - Unmatched properties from the message are deserialized this collection
 | ||
|  | 	AdditionalProperties map[string]interface{} `json:""` | ||
|  | 	// Start - Start time of the metric segment (only when an interval was specified).
 | ||
|  | 	Start time.Time `json:"start,omitempty"` | ||
|  | 	// End - Start time of the metric segment (only when an interval was specified).
 | ||
|  | 	End time.Time `json:"end,omitempty"` | ||
|  | 	// Segments - Segmented metric data (if further segmented).
 | ||
|  | 	Segments *[]MetricsSegmentInfo `json:"segments,omitempty"` | ||
|  | } | ||
|  | 
 | ||
|  | // UnmarshalJSON is the custom unmarshaler for MetricsSegmentInfo struct.
 | ||
|  | // This is copied from azure-sdk-for-go/services/preview/appinsights/v1/insights (except time Type is changed).
 | ||
|  | func (mri *MetricsSegmentInfo) UnmarshalJSON(body []byte) error { | ||
|  | 	var m map[string]*json.RawMessage | ||
|  | 	err := json.Unmarshal(body, &m) | ||
|  | 	if err != nil { | ||
|  | 		return err | ||
|  | 	} | ||
|  | 	for k, v := range m { | ||
|  | 		switch k { | ||
|  | 		default: | ||
|  | 			if v != nil { | ||
|  | 				var additionalProperties interface{} | ||
|  | 				err = json.Unmarshal(*v, &additionalProperties) | ||
|  | 				if err != nil { | ||
|  | 					return err | ||
|  | 				} | ||
|  | 				if mri.AdditionalProperties == nil { | ||
|  | 					mri.AdditionalProperties = make(map[string]interface{}) | ||
|  | 				} | ||
|  | 				mri.AdditionalProperties[k] = additionalProperties | ||
|  | 			} | ||
|  | 		case "start": | ||
|  | 			if v != nil { | ||
|  | 				var start time.Time | ||
|  | 				err = json.Unmarshal(*v, &start) | ||
|  | 				if err != nil { | ||
|  | 					return err | ||
|  | 				} | ||
|  | 				mri.Start = start | ||
|  | 			} | ||
|  | 		case "end": | ||
|  | 			if v != nil { | ||
|  | 				var end time.Time | ||
|  | 				err = json.Unmarshal(*v, &end) | ||
|  | 				if err != nil { | ||
|  | 					return err | ||
|  | 				} | ||
|  | 				mri.End = end | ||
|  | 			} | ||
|  | 		case "segments": | ||
|  | 			if v != nil { | ||
|  | 				var segments []MetricsSegmentInfo | ||
|  | 				err = json.Unmarshal(*v, &segments) | ||
|  | 				if err != nil { | ||
|  | 					return err | ||
|  | 				} | ||
|  | 				mri.Segments = &segments | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	return nil | ||
|  | } | ||
|  | 
 | ||
|  | // UnmarshalJSON is the custom unmarshaler for MetricsResultInfo struct.
 | ||
|  | // This is copied from azure-sdk-for-go/services/preview/appinsights/v1/insights (except time Type is changed).
 | ||
|  | func (mri *MetricsResultInfo) UnmarshalJSON(body []byte) error { | ||
|  | 	var m map[string]*json.RawMessage | ||
|  | 	err := json.Unmarshal(body, &m) | ||
|  | 	if err != nil { | ||
|  | 		return err | ||
|  | 	} | ||
|  | 	for k, v := range m { | ||
|  | 		switch k { | ||
|  | 		default: | ||
|  | 			if v != nil { | ||
|  | 				var additionalProperties interface{} | ||
|  | 				err = json.Unmarshal(*v, &additionalProperties) | ||
|  | 				if err != nil { | ||
|  | 					return err | ||
|  | 				} | ||
|  | 				if mri.AdditionalProperties == nil { | ||
|  | 					mri.AdditionalProperties = make(map[string]interface{}) | ||
|  | 				} | ||
|  | 				mri.AdditionalProperties[k] = additionalProperties | ||
|  | 			} | ||
|  | 		case "start": | ||
|  | 			if v != nil { | ||
|  | 				var start time.Time | ||
|  | 				err = json.Unmarshal(*v, &start) | ||
|  | 				if err != nil { | ||
|  | 					return err | ||
|  | 				} | ||
|  | 				mri.Start = start | ||
|  | 			} | ||
|  | 		case "end": | ||
|  | 			if v != nil { | ||
|  | 				var end time.Time | ||
|  | 				err = json.Unmarshal(*v, &end) | ||
|  | 				if err != nil { | ||
|  | 					return err | ||
|  | 				} | ||
|  | 				mri.End = end | ||
|  | 			} | ||
|  | 		case "interval": | ||
|  | 			if v != nil { | ||
|  | 				var interval string | ||
|  | 				err = json.Unmarshal(*v, &interval) | ||
|  | 				if err != nil { | ||
|  | 					return err | ||
|  | 				} | ||
|  | 				mri.Interval = &interval | ||
|  | 			} | ||
|  | 		case "segments": | ||
|  | 			if v != nil { | ||
|  | 				var segments []MetricsSegmentInfo | ||
|  | 				err = json.Unmarshal(*v, &segments) | ||
|  | 				if err != nil { | ||
|  | 					return err | ||
|  | 				} | ||
|  | 				mri.Segments = &segments | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	return nil | ||
|  | } |