Skip to content

Commit 0d8476e

Browse files
committed
Sync from Bitbucket
1 parent 43c681e commit 0d8476e

File tree

7 files changed

+330
-66
lines changed

7 files changed

+330
-66
lines changed

src/coscale/api/common.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ func (api *Api) GetObjectRefFromGroup(objectGroup, objectName string, groupID, o
5353

5454
//GetObjectByName will return the object (json) specified by objectName and name
5555
func (api *Api) GetObjectByName(objectName string, name string) (string, error) {
56+
// In go %% is % escaped, we need to escape the name to work with string fmt.
57+
name = strings.Replace(name, "%", "%%", -1)
5658
name = strings.Replace(name, " ", "%20", -1)
5759
var result string
5860
if err := api.makeCall("GET", fmt.Sprintf("/api/v1/app/%s/%ss/?selectByName=%s", api.AppID, objectName, name), nil, true, &result); err != nil {

src/coscale/api/data.go

Lines changed: 90 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,36 @@ package api
22

33
import (
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+
1116
type 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

1737
type 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.
6095
func 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

src/coscale/api/dimension.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package api
2+
3+
import "fmt"
4+
5+
// Dimension is a metric dimension.
6+
type Dimension struct {
7+
ID int64
8+
Name string
9+
// Link
10+
}
11+
12+
func (d *Dimension) String() string {
13+
return fmt.Sprintf("%#v", d)
14+
}
15+
16+
// GetDimension gets one dimension by its id.
17+
func (api *Api) GetDimension(id int64) (string, error) {
18+
var result string
19+
if err := api.makeCall("GET", fmt.Sprintf("/api/v1/app/%s/dimensions/%d/", api.AppID, id), nil, true, &result); err != nil {
20+
return "", err
21+
}
22+
return result, nil
23+
}
24+
25+
// GetDimensions gets the dimensions for a metric
26+
func (api *Api) GetDimensions(metricID int64) (string, error) {
27+
var result string
28+
if err := api.makeCall("GET", fmt.Sprintf("/api/v1/app/%s/metrics/%d/dimensions/", api.AppID, metricID), nil, true, &result); err != nil {
29+
return "", err
30+
}
31+
return result, nil
32+
}
33+
34+
// CreateDimension creates a new dimension.
35+
func (api *Api) CreateDimension(name string) (string, error) {
36+
data := map[string][]string{
37+
"name": {name},
38+
}
39+
40+
var result string
41+
if err := api.makeCall("POST", fmt.Sprintf("/api/v1/app/%s/dimensions/", api.AppID), data, true, &result); err != nil {
42+
if duplicate, id := IsDuplicate(err); duplicate {
43+
return api.GetDimension(id)
44+
}
45+
return "", err
46+
}
47+
48+
return result, nil
49+
}

src/coscale/api/metric.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ type Metric struct {
1313
Period int
1414
Unit string
1515
Source string
16+
Subject string
1617
State string
1718
Version int64
1819
}
@@ -64,6 +65,7 @@ func (api *Api) UpdateMetric(metric *Metric) (string, error) {
6465
"dataType": {metric.DataType},
6566
"period": {strconv.Itoa(metric.Period)},
6667
"unit": {metric.Unit},
68+
"subject": {metric.Subject},
6769
"source": {metric.Source},
6870
"version": {fmt.Sprintf("%d", metric.Version)},
6971
}
@@ -100,8 +102,10 @@ func (api *Api) UpdateMetricGroup(metricGroup *MetricGroup) (string, error) {
100102
"name": {metricGroup.Name},
101103
"description": {metricGroup.Description},
102104
"type": {metricGroup.Type},
105+
"state": {metricGroup.State},
103106
"subject": {metricGroup.Subject},
104107
"source": {metricGroup.Source},
108+
"version": {fmt.Sprintf("%d", metricGroup.Version)},
105109
}
106110
var result string
107111
if err := api.makeCall("PUT", fmt.Sprintf("/api/v1/app/%s/metricgroups/%d/", api.AppID, metricGroup.ID), data, true, &result); err != nil {
@@ -118,3 +122,12 @@ func (api *Api) GetMetricsByGroup(metricGroup *MetricGroup) (string, error) {
118122
}
119123
return result, nil
120124
}
125+
126+
// AddMetricDimension adds a dimension to a metric
127+
func (api *Api) AddMetricDimension(metricID, dimensionID int64) (string, error) {
128+
var result string
129+
if err := api.makeCall("POST", fmt.Sprintf("/api/v1/app/%s/metrics/%d/dimensions/%d/", api.AppID, metricID, dimensionID), nil, true, &result); err != nil {
130+
return "", err
131+
}
132+
return result, nil
133+
}

src/coscale/command/common.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,11 @@ Mandatory:
157157
if err != nil {
158158
cmd.PrintResult("", err)
159159
}
160+
160161
if idGroup != -1 {
161162
err = cmd.Capi.GetObjectRef(fmt.Sprintf("%sgroup", objectName), idGroup, group)
162163
} else if nameObject != DEFAULT_STRING_FLAG_VALUE {
163-
err = cmd.Capi.GetObejctRefByName(fmt.Sprintf("%sgroup", objectName), nameObject, group)
164+
err = cmd.Capi.GetObejctRefByName(fmt.Sprintf("%sgroup", objectName), nameGroup, group)
164165
} else {
165166
cmd.PrintUsage()
166167
os.Exit(EXIT_FLAG_ERROR)
@@ -220,7 +221,7 @@ Mandatory:
220221
if idGroup != -1 {
221222
err = cmd.Capi.GetObjectRef(fmt.Sprintf("%sgroup", objectName), idGroup, group)
222223
} else if nameObject != DEFAULT_STRING_FLAG_VALUE {
223-
err = cmd.Capi.GetObejctRefByName(fmt.Sprintf("%sgroup", objectName), nameObject, group)
224+
err = cmd.Capi.GetObejctRefByName(fmt.Sprintf("%sgroup", objectName), nameGroup, group)
224225
} else {
225226
cmd.PrintUsage()
226227
os.Exit(EXIT_FLAG_ERROR)

0 commit comments

Comments
 (0)