@@ -2,16 +2,36 @@ package api
22
33import (
44 "bytes"
5+ "encoding/json"
56 "fmt"
7+ "regexp"
68 "strconv"
79 "strings"
810 "time"
911)
1012
13+ // dataPattern is used to split into groups the data inserted by the user.
14+ var dataPattern = regexp .MustCompile (`(M[0-9]+):([ASG]{1}[0-9]*):(-?[0-9]+):([0-9.]+)(?:\:(\{(?:.*?)\}))?;` )
15+
1116type ApiData struct {
12- MetricID int64
13- SubjectID string
14- Data []DataPoint
17+ MetricID int64
18+ SubjectID string
19+ Data []DataPoint
20+ DimensionValues map [string ]string
21+ }
22+
23+ // HasDimensions will check the ApiData has exactly those dimensions.
24+ func (a * ApiData ) HasDimensions (dimensions map [string ]string ) bool {
25+ if len (a .DimensionValues ) != len (dimensions ) {
26+ return false
27+ }
28+ // Check if the two maps are equal.
29+ for key , val := range a .DimensionValues {
30+ if dim , ok := dimensions [key ]; ! (ok && val == dim ) {
31+ return false
32+ }
33+ }
34+ return true
1535}
1636
1737type DataPoint struct {
@@ -32,7 +52,22 @@ func (data *ApiData) String() string {
3252 }
3353 buffer .WriteString (d .String ())
3454 }
35- buffer .WriteString ("]}" )
55+ buffer .WriteString ("]" )
56+
57+ if len (data .DimensionValues ) > 0 {
58+ buffer .WriteString (`,"dv":{` )
59+ index := 0
60+ for dimension , dimensionValue := range data .DimensionValues {
61+ if index > 0 {
62+ buffer .WriteString ("," )
63+ }
64+ buffer .WriteString (fmt .Sprintf (`"%s":"%s"` , dimension , dimensionValue ))
65+ index ++
66+ }
67+ buffer .WriteString ("}" )
68+ }
69+
70+ buffer .WriteString ("}" )
3671 return buffer .String ()
3772}
3873
@@ -49,67 +84,84 @@ func apiDataToString(data []*ApiData) string {
4984 return buffer .String ()
5085}
5186
52- //dataPoint is provided by user on the command line
53- //the format is this:
54- // <METRIC>:<SUBJECT>:<TIME>[<SAMPLES>,<PERCENTILE WIDTH>,[<PERCENTILE DATA>]]
55- // eg: M1:s1 :-60:[100,50,[1,2,3,4,5,6]]
87+ // ParseDataPoint will parse a dataPoint which is provided by user on the command line
88+ // the format is this:
89+ // <METRIC>:<SUBJECT>:<TIME>: [<SAMPLES>,<PERCENTILE WIDTH>,[<PERCENTILE DATA>]]:<{"DIMENSIONS": "JSON"}>
90+ // eg: M1:S1 :-60:[100,50,[1,2,3,4,5,6]]:{"Queue":"q1","Data Center":"data center 1"}
5691// Multiple dataPoint can be splited by semicolons
57- // eg: M1:s1:-120:0.1;M1:s1 :-60:90.9
92+ // eg: M1:S1:-60:1.3:{"Queue":"q1","Data Center":"data center 1"};M2:S1 :-60:1.2
5893// If timeInSecAgo is true, the time should be positive and is the number of seconds ago. Otherwise
5994// it is the time format as defined by the api.
6095func ParseDataPoint (dataPoint string , timeInSecAgo bool ) (map [string ][]* ApiData , error ) {
61- dataPoint = strings .TrimRight (dataPoint , ";" )
62- // split the data in multiple calls if multiple subjectIds are provided
63- // we do this because a call cannot have the same metricId two times, but we should allow adding data
64- // for the same metricId on multiple subject ids
96+
97+ // add semicolon at the end if is neccessary, it will help for better matching.
98+ if (dataPoint [len (dataPoint )- 1 ]) != ';' {
99+ dataPoint += `;`
100+ }
101+
65102 callsData := make (map [string ][]* ApiData )
103+ // Match the received data against the dataPattern and extract the expected fields.
104+ matches := dataPattern .FindAllStringSubmatch (dataPoint , - 1 )
105+
106+ if len (matches ) == 0 {
107+ return nil , fmt .Errorf ("Bad datapoint format" )
108+ }
109+
110+ for _ , match := range matches {
66111
67- // the data entries are separated by ";"
68- for _ , entry := range strings .Split (dataPoint , ";" ) {
69- // parse each data entry
70- data := strings .Split (entry , ":" )
71- if len (data ) < 4 {
112+ // The match should have a certain lenght even if dimension values are missing.
113+ if len (match ) != 6 {
72114 return nil , fmt .Errorf ("Bad datapoint format" )
73115 }
74- metricId , err := strconv .ParseInt (data [0 ][1 :], 10 , 64 )
116+
117+ // Parse the metric id into int64
118+ metricID , err := strconv .ParseInt (match [1 ][1 :], 10 , 64 )
75119 if err != nil {
76120 return nil , err
77121 }
78- time , err := strconv .Atoi (data [2 ])
122+
123+ subjectID := match [2 ]
124+
125+ time , err := strconv .Atoi (match [3 ])
79126 if err != nil {
80127 return nil , err
81128 }
82- subjectId := data [1 ]
83129
84- // Convert the time format if timeInSecAgo is true.
130+ // Convert the time format if timeInSecAgo is true. (Is for the deprecated call)
85131 if timeInSecAgo {
86132 time = - time
87133 }
134+ var dimValues map [string ]string
135+ if len (match [5 ]) > 0 {
136+ if err := json .Unmarshal ([]byte (match [5 ]), & dimValues ); err != nil {
137+ return nil , err
138+ }
139+ }
88140
89141 // create the new ApiData object
90- newDataPoint := DataPoint {time , data [ 3 ]}
91- newApiData := & ApiData {metricId , subjectId , []DataPoint {newDataPoint }}
142+ newDataPoint := DataPoint {time , match [ 4 ]}
143+ newAPIData := & ApiData {metricID , subjectID , []DataPoint {newDataPoint }, dimValues }
92144
93145 // find the right place for this ApiData in the result
94146 // if a callData for this subjectId exists, then the new data belongs to it
95- if callData , found := callsData [subjectId ]; found {
147+ if callData , found := callsData [subjectID ]; found {
96148 // search for an existing ApiData for this metricId
97- var foundMetricId bool
149+ var foundMetricID bool
98150 for _ , apiData := range callData {
99- if apiData .MetricID == metricId {
151+ if apiData .MetricID == metricID && apiData . HasDimensions ( dimValues ) {
100152 // found the apiData, append to it just the new dataPoint
101153 apiData .Data = append (apiData .Data , newDataPoint )
102- foundMetricId = true
154+ foundMetricID = true
103155 break
104156 }
105157 }
106158 // a apiData with the same metric Id doesn't exists, create a new one
107- if ! foundMetricId {
108- callsData [subjectId ] = append (callsData [subjectId ], newApiData )
159+ if ! foundMetricID {
160+ callsData [subjectID ] = append (callsData [subjectID ], newAPIData )
109161 }
110162 } else {
111163 // this is a new subjectId, create a new callData
112- callsData [subjectId ] = []* ApiData {newApiData }
164+ callsData [subjectID ] = []* ApiData {newAPIData }
113165 }
114166 }
115167 return callsData , nil
@@ -129,7 +181,7 @@ func (api *Api) InsertData(data []*ApiData) (string, error) {
129181}
130182
131183//make the json object(with the informations provided on command line) required for GetData-getBatch request
132- func getBatchData (start , stop int , metricId int64 , subjectIds , aggregator string , aggregateSubjects bool ) string {
184+ func getBatchData (start , stop int , metricId int64 , subjectIds , aggregator , dimensionsSpecs string , aggregateSubjects bool ) string {
133185 var buffer bytes.Buffer
134186 var now = int (time .Now ().Unix ())
135187 // negative and null values are seconds ago
@@ -139,23 +191,23 @@ func getBatchData(start, stop int, metricId int64, subjectIds, aggregator string
139191 if stop <= 0 {
140192 stop = now + stop
141193 }
142- buffer .WriteString (fmt .Sprintf (`{"start":%d, "stop":%d, "ids":[{"metricId":%d, "subjectIds":[ ` , start , stop , metricId ))
194+ buffer .WriteString (fmt .Sprintf (`{"start":%d, "stop":%d, "ids":[{"metricId":%d, "subjects":" ` , start , stop , metricId ))
143195 for i , id := range strings .Split (subjectIds , "," ) {
144196 if i > 0 {
145197 buffer .WriteString ("," )
146198 }
147- buffer .WriteString (fmt .Sprintf (`"%s" ` , id ))
199+ buffer .WriteString (fmt .Sprintf (`%s ` , id ))
148200 }
149- buffer .WriteString (fmt .Sprintf (`] , "aggregator":"%s", "aggregateSubjects":%t}]}` , aggregator , aggregateSubjects ))
201+ buffer .WriteString (fmt .Sprintf (`" , "aggregator":"%s", "dimensionsSpecs":%s, " aggregateSubjects":%t}]}` , aggregator , dimensionsSpecs , aggregateSubjects ))
150202 return buffer .String ()
151203}
152204
153- func (api * Api ) GetData (start , stop int , metricId int64 , subjectIds , aggregator string , aggregateSubjects bool ) (string , error ) {
205+ func (api * Api ) GetData (start , stop int , metricId int64 , subjectIds , aggregator , dimensionsSpecs string , aggregateSubjects bool ) (string , error ) {
154206 postData := map [string ][]string {
155- "data" : {getBatchData (start , stop , metricId , subjectIds , aggregator , aggregateSubjects )},
207+ "data" : {getBatchData (start , stop , metricId , subjectIds , aggregator , dimensionsSpecs , aggregateSubjects )},
156208 }
157209 var result string
158- if err := api .makeCall ("POST" , fmt .Sprintf ("/api/v1/app/%s/data/getBatch /" , api .AppID ), postData , true , & result ); err != nil {
210+ if err := api .makeCall ("POST" , fmt .Sprintf ("/api/v1/app/%s/data/dimension/getCalculated /" , api .AppID ), postData , true , & result ); err != nil {
159211 return "" , err
160212 }
161213 return result , nil
0 commit comments