Skip to content

Commit 08dacaa

Browse files
diemolclaude
andauthored
Implement the Performance API (v2/performance/metrics) (#630)
Fills in PerformanceEndpoint, which was previously an empty stub annotated @unfinished. Adds all 11 documented endpoints: performance results (account-wide and per-test), test assertions, baseline (read/reset), discarded-outlier history and management, test history, and metric regimes (read/acknowledge). Adds matching parameter builders and response models under model.performance, following the same conventions used for the Insights endpoints. Fixes #150 Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
1 parent be96032 commit 08dacaa

16 files changed

Lines changed: 594 additions & 3 deletions

‎src/main/java/com/saucelabs/saucerest/api/PerformanceEndpoint.java‎

Lines changed: 261 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
11
package com.saucelabs.saucerest.api;
22

3+
import com.google.gson.reflect.TypeToken;
34
import com.saucelabs.saucerest.DataCenter;
4-
import com.saucelabs.saucerest.Unfinished;
5+
import com.saucelabs.saucerest.HttpMethod;
6+
import com.saucelabs.saucerest.model.performance.Baseline;
7+
import com.saucelabs.saucerest.model.performance.BaselineParameter;
8+
import com.saucelabs.saucerest.model.performance.BaselineResetStatus;
9+
import com.saucelabs.saucerest.model.performance.DiscardedTests;
10+
import com.saucelabs.saucerest.model.performance.GetPerformanceMetricsParameter;
11+
import com.saucelabs.saucerest.model.performance.MetricAssertion;
12+
import com.saucelabs.saucerest.model.performance.PerformanceMetrics;
13+
import com.saucelabs.saucerest.model.performance.Regime;
14+
import java.io.IOException;
15+
import java.lang.reflect.Type;
16+
import java.util.HashMap;
17+
import java.util.List;
18+
import java.util.Map;
19+
import okhttp3.Response;
520

6-
@Unfinished("This endpoint is not yet completely implemented")
721
public class PerformanceEndpoint extends AbstractEndpoint {
822
public PerformanceEndpoint(DataCenter dataCenter) {
923
super(dataCenter);
@@ -20,4 +34,248 @@ public PerformanceEndpoint(String username, String accessKey, DataCenter dataCen
2034
public PerformanceEndpoint(String username, String accessKey, String apiServer) {
2135
super(username, accessKey, apiServer);
2236
}
23-
}
37+
38+
/**
39+
* Retrieves performance test results and metric values for the requesting account.
40+
*
41+
* @param parameter A {@link GetPerformanceMetricsParameter} object containing the parameters to filter the
42+
* results
43+
* @return A {@link PerformanceMetrics} object
44+
* @throws IOException when the request fails
45+
*/
46+
public PerformanceMetrics getPerformanceResults(GetPerformanceMetricsParameter parameter) throws IOException {
47+
return deserializeJSONObject(
48+
requestWithQueryParameters(getBaseEndpoint(), HttpMethod.GET, parameter.toMap()), PerformanceMetrics.class);
49+
}
50+
51+
/**
52+
* Retrieves the full results for a specific performance test run.
53+
*
54+
* @param jobID The unique identifier of the test
55+
* @return A {@link PerformanceMetrics} object
56+
* @throws IOException when the request fails
57+
*/
58+
public PerformanceMetrics getPerformanceResults(String jobID) throws IOException {
59+
return getPerformanceResults(jobID, true);
60+
}
61+
62+
/**
63+
* Retrieves the results for a specific performance test run.
64+
*
65+
* @param jobID The unique identifier of the test
66+
* @param full When {@code false}, only basic data is returned instead of the full metric set
67+
* @return A {@link PerformanceMetrics} object
68+
* @throws IOException when the request fails
69+
*/
70+
public PerformanceMetrics getPerformanceResults(String jobID, boolean full) throws IOException {
71+
Map<String, Object> params = new HashMap<>();
72+
params.put("full", full);
73+
74+
return deserializeJSONObject(
75+
requestWithQueryParameters(getBaseEndpoint(jobID), HttpMethod.GET, params), PerformanceMetrics.class);
76+
}
77+
78+
/**
79+
* Returns information about outlier values in the test for the specified metrics.
80+
*
81+
* @param jobID The unique identifier of the test
82+
* @param metricNames The metrics to evaluate
83+
* @param orderIndex The record number to begin returning results from
84+
* @return A {@link Map} of metric name to {@link MetricAssertion}
85+
* @throws IOException when the request fails
86+
*/
87+
public Map<String, MetricAssertion> getTestAssertions(String jobID, String[] metricNames, int orderIndex)
88+
throws IOException {
89+
Map<String, Object> params = new HashMap<>();
90+
params.put("metric_names", metricNames);
91+
params.put("order_index", orderIndex);
92+
93+
Type type = TypeToken.getParameterized(Map.class, String.class, MetricAssertion.class).getType();
94+
return deserializeJSON(requestWithQueryParameters(getBaseEndpoint(jobID) + "assert/", HttpMethod.GET, params), type);
95+
}
96+
97+
/**
98+
* Returns the acceptable upper and lower boundary values for the specified metrics, as determined by the test's
99+
* baseline.
100+
*
101+
* @param jobID The unique identifier of the test
102+
* @param parameter A {@link BaselineParameter} object containing the parameters to filter the results
103+
* @return A {@link Map} of metric name to {@link Baseline}
104+
* @throws IOException when the request fails
105+
*/
106+
public Map<String, Baseline> getTestBaseline(String jobID, BaselineParameter parameter) throws IOException {
107+
Type type = TypeToken.getParameterized(Map.class, String.class, Baseline.class).getType();
108+
return deserializeJSON(
109+
requestWithQueryParameters(getBaseEndpoint(jobID) + "baseline/", HttpMethod.GET, parameter.toMap()), type);
110+
}
111+
112+
/**
113+
* Indicates whether a baseline reset has occurred for the specified test.
114+
*
115+
* @param jobID The unique identifier of the test
116+
* @return A {@link BaselineResetStatus} object
117+
* @throws IOException when the request fails
118+
*/
119+
public BaselineResetStatus getBaselineResetHistory(String jobID) throws IOException {
120+
String url = getBaseEndpoint(jobID) + "baseline/reset/";
121+
122+
return deserializeJSONObject(request(url, HttpMethod.GET), BaselineResetStatus.class);
123+
}
124+
125+
/**
126+
* Resets the baseline calculation point for the specified test; tests run prior to the reset are excluded from
127+
* future baseline calculations.
128+
*
129+
* @param jobID The unique identifier of the test
130+
* @return {@link Response}
131+
* @throws IOException when the request fails
132+
*/
133+
public Response resetBaseline(String jobID) throws IOException {
134+
String url = getBaseEndpoint(jobID) + "baseline/reset/";
135+
136+
return request(url, HttpMethod.POST);
137+
}
138+
139+
/**
140+
* Returns the list of tests that have been discarded from the baseline as outliers.
141+
*
142+
* @param jobID The unique identifier of the test
143+
* @param orderIndex The record number to begin returning results from
144+
* @return A {@link DiscardedTests} object
145+
* @throws IOException when the request fails
146+
*/
147+
public DiscardedTests getDiscardedTests(String jobID, int orderIndex) throws IOException {
148+
return getDiscardedTests(jobID, orderIndex, null);
149+
}
150+
151+
/**
152+
* Returns the list of tests that have been discarded from the baseline as outliers.
153+
*
154+
* @param jobID The unique identifier of the test
155+
* @param orderIndex The record number to begin returning results from
156+
* @param limit The maximum number of results to return
157+
* @return A {@link DiscardedTests} object
158+
* @throws IOException when the request fails
159+
*/
160+
public DiscardedTests getDiscardedTests(String jobID, int orderIndex, Integer limit) throws IOException {
161+
Map<String, Object> params = new HashMap<>();
162+
params.put("order_index", orderIndex);
163+
if (limit != null) {
164+
params.put("limit", limit);
165+
}
166+
167+
return deserializeJSONObject(
168+
requestWithQueryParameters(getBaseEndpoint(jobID) + "discarded/", HttpMethod.GET, params), DiscardedTests.class);
169+
}
170+
171+
/**
172+
* Excludes outlier results from future baseline calculations, marking them as flaky.
173+
*
174+
* @param jobID The unique identifier of the test
175+
* @param orderIndex The record number to begin discarding results from
176+
* @return {@link Response}
177+
* @throws IOException when the request fails
178+
*/
179+
public Response discardOutliers(String jobID, int orderIndex) throws IOException {
180+
Map<String, Object> params = new HashMap<>();
181+
params.put("order_index", orderIndex);
182+
183+
return requestWithQueryParameters(getBaseEndpoint(jobID) + "discarded/", HttpMethod.POST, params);
184+
}
185+
186+
/**
187+
* Returns the test history for the specified test.
188+
*
189+
* @param jobID The unique identifier of the test
190+
* @param orderIndex The record number to begin returning results from
191+
* @return A {@link PerformanceMetrics} object
192+
* @throws IOException when the request fails
193+
*/
194+
public PerformanceMetrics getTestHistory(String jobID, int orderIndex) throws IOException {
195+
return getTestHistory(jobID, orderIndex, null);
196+
}
197+
198+
/**
199+
* Returns the test history for the specified test.
200+
*
201+
* @param jobID The unique identifier of the test
202+
* @param orderIndex The record number to begin returning results from
203+
* @param limit The maximum number of results to return
204+
* @return A {@link PerformanceMetrics} object
205+
* @throws IOException when the request fails
206+
*/
207+
public PerformanceMetrics getTestHistory(String jobID, int orderIndex, Integer limit) throws IOException {
208+
Map<String, Object> params = new HashMap<>();
209+
params.put("order_index", orderIndex);
210+
if (limit != null) {
211+
params.put("limit", limit);
212+
}
213+
214+
return deserializeJSONObject(
215+
requestWithQueryParameters(getBaseEndpoint(jobID) + "history/", HttpMethod.GET, params), PerformanceMetrics.class);
216+
}
217+
218+
/**
219+
* Returns the starting and ending job counts in the current regime for each of the specified metrics.
220+
*
221+
* @param jobID The unique identifier of the test
222+
* @param metricNames The metrics to return regime information for
223+
* @param orderIndex The record number to limit results to
224+
* @return A {@link Map} of metric name to a {@link List} of {@link Regime} objects
225+
* @throws IOException when the request fails
226+
*/
227+
public Map<String, List<Regime>> getMetricRegimes(String jobID, String[] metricNames, int orderIndex)
228+
throws IOException {
229+
return getMetricRegimes(jobID, metricNames, orderIndex, null);
230+
}
231+
232+
/**
233+
* Returns the starting and ending job counts in the current regime for each of the specified metrics.
234+
*
235+
* @param jobID The unique identifier of the test
236+
* @param metricNames The metrics to return regime information for
237+
* @param orderIndex The record number to limit results to
238+
* @param includeBaseline Whether to include baseline values
239+
* @return A {@link Map} of metric name to a {@link List} of {@link Regime} objects
240+
* @throws IOException when the request fails
241+
*/
242+
public Map<String, List<Regime>> getMetricRegimes(
243+
String jobID, String[] metricNames, int orderIndex, Boolean includeBaseline) throws IOException {
244+
Map<String, Object> params = new HashMap<>();
245+
params.put("metric_names", metricNames);
246+
params.put("order_index", orderIndex);
247+
if (includeBaseline != null) {
248+
params.put("include_baseline", includeBaseline);
249+
}
250+
251+
Type type = TypeToken.getParameterized(
252+
Map.class, String.class, TypeToken.getParameterized(List.class, Regime.class).getType()).getType();
253+
return deserializeJSON(
254+
requestWithQueryParameters(getBaseEndpoint(jobID) + "regimes/", HttpMethod.GET, params), type);
255+
}
256+
257+
/**
258+
* Confirms that the new regime values for the specified test are acceptable.
259+
*
260+
* @param jobID The unique identifier of the test
261+
* @param orderIndex The record number to limit results to
262+
* @return {@link Response}
263+
* @throws IOException when the request fails
264+
*/
265+
public Response acknowledgeRegimes(String jobID, int orderIndex) throws IOException {
266+
Map<String, Object> params = new HashMap<>();
267+
params.put("order_index", orderIndex);
268+
269+
return requestWithQueryParameters(getBaseEndpoint(jobID) + "regimes/acknowledge/", HttpMethod.POST, params);
270+
}
271+
272+
/** The base endpoint of the Performance endpoint APIs. */
273+
protected String getBaseEndpoint() {
274+
return super.getBaseEndpoint() + "v2/performance/metrics/";
275+
}
276+
277+
/** The base endpoint of the Performance endpoint APIs for a specific test. */
278+
protected String getBaseEndpoint(String jobID) {
279+
return getBaseEndpoint() + jobID + "/";
280+
}
281+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.saucelabs.saucerest.model.performance;
2+
3+
import java.util.List;
4+
5+
public class Baseline {
6+
public Double upperBoundary;
7+
public Double lowerBoundary;
8+
public Double baseline;
9+
public List<BaselineValue> values;
10+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package com.saucelabs.saucerest.model.performance;
2+
3+
import java.util.AbstractMap;
4+
import java.util.HashMap;
5+
import java.util.Map;
6+
import java.util.stream.Stream;
7+
8+
/** Query parameters for {@code GET /v2/performance/metrics/{job_id}/baseline/}. */
9+
public class BaselineParameter {
10+
private final String[] metricNames;
11+
private final int orderIndex;
12+
private final Integer regimeStart;
13+
private final Integer regimeEnd;
14+
15+
private BaselineParameter(Builder builder) {
16+
metricNames = builder.metricNames;
17+
orderIndex = builder.orderIndex;
18+
regimeStart = builder.regimeStart;
19+
regimeEnd = builder.regimeEnd;
20+
}
21+
22+
public Map<String, Object> toMap() {
23+
Map<String, Object> parameters = new HashMap<>();
24+
25+
Stream.of(
26+
new AbstractMap.SimpleEntry<>("metric_names", metricNames),
27+
new AbstractMap.SimpleEntry<>("order_index", orderIndex),
28+
new AbstractMap.SimpleEntry<>("regime_start", regimeStart),
29+
new AbstractMap.SimpleEntry<>("regime_end", regimeEnd)
30+
)
31+
.filter(e -> e.getValue() != null)
32+
.forEach(e -> parameters.put(e.getKey(), e.getValue()));
33+
34+
return parameters;
35+
}
36+
37+
public static final class Builder {
38+
private final String[] metricNames;
39+
private final int orderIndex;
40+
private Integer regimeStart;
41+
private Integer regimeEnd;
42+
43+
/**
44+
* @param metricNames The metrics to return baseline information for (required).
45+
* @param orderIndex The record number to begin returning results from (required).
46+
*/
47+
public Builder(String[] metricNames, int orderIndex) {
48+
this.metricNames = metricNames;
49+
this.orderIndex = orderIndex;
50+
}
51+
52+
public Builder setRegimeStart(Integer val) {
53+
regimeStart = val;
54+
return this;
55+
}
56+
57+
public Builder setRegimeEnd(Integer val) {
58+
regimeEnd = val;
59+
return this;
60+
}
61+
62+
public BaselineParameter build() {
63+
return new BaselineParameter(this);
64+
}
65+
}
66+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.saucelabs.saucerest.model.performance;
2+
3+
public class BaselineResetStatus {
4+
public Boolean result;
5+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.saucelabs.saucerest.model.performance;
2+
3+
public class BaselineValue {
4+
public Double realValue;
5+
public String datetime;
6+
public String jobId;
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.saucelabs.saucerest.model.performance;
2+
3+
import java.util.List;
4+
5+
public class DiscardedTests {
6+
public List<String> jobIds;
7+
}

0 commit comments

Comments
 (0)