Skip to content

Commit

Permalink
start to structure query templating cleanly and to avoid argument con…
Browse files Browse the repository at this point in the history
…fusion
  • Loading branch information
KevinMGranger committed Feb 7, 2024
1 parent e706113 commit 5720027
Showing 1 changed file with 119 additions and 59 deletions.
178 changes: 119 additions & 59 deletions src/main/java/api/pelorus/org/SoftwareDeliveryPerformanceApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,46 +30,86 @@
import io.prometheus.api.QueryService;
import io.prometheus.api.Value;

import lombok.Builder;

@Path("/sdp")
public class SoftwareDeliveryPerformanceApi {

private final String APPS_LIST = """
group(
count_over_time(commit_timestamp [%1$s]) or
count_over_time(deploy_timestamp [%1$s]) or
count_over_time(failure_creation_timestamp [%1$s]) or
count_over_time(failure_resolution_timestamp[%1$s])
) by (app)
""";

//#region Lead Time
private final String LEAD_TIME_FOR_CHANGE = "avg_over_time(sdp:lead_time:global [%s])";
private final String LEAD_TIME_FOR_CHANGE_BY_APP = "avg_over_time(sdp:lead_time:by_app{app=~'.*%s.*'}[%s])";
private final String LEAD_TIME_FOR_CHANGE_BY_APP_OFFSET = "avg_over_time(sdp:lead_time:by_app{app=~'.*%1$s.*'}[%2$s] offset %2$s)";
// It should be this, once https://github.com/dora-metrics/pelorus/issues/1088 gets resolved
private final String LEAD_TIME_FOR_CHANGE_BY_APP_DATA = "sdp:lead_time:by_commit{app=~'.*%s.*'}[%s]";
// private final String LEAD_TIME_FOR_CHANGE_BY_APP_DATA = "(min_over_time(deploy_timestamp{app=~\".*%1$s.*\"}[%2$s]) - on(app,image_sha) group_left(commit) (max by (app, commit, image_sha) (max_over_time(commit_timestamp{app=~\".*%1$s.*\"}[%2$s]))))";
//#endregion

//#region Deployment Freq
group(
count_over_time(commit_timestamp [%1$s]) or
count_over_time(deploy_timestamp [%1$s]) or
count_over_time(failure_creation_timestamp [%1$s]) or
count_over_time(failure_resolution_timestamp[%1$s])
) by (app)
""";

/**
* App and Range arguments for templates so the arguments don't get confused.
*/
@Builder
static record AppRange(String app, String range) {
}

/**
* App, Range, and At arguments for templates so the arguments don't get
* confused.
*/
@Builder
static record AppRangeAt(String app, String range, String at) {
}

// private static class LeadTimeForChange {
static final String LEAD_TIME_FOR_CHANGE = "avg_over_time(sdp:lead_time:global [%s])";
static final String LEAD_TIME_FOR_CHANGE_BY_APP = "avg_over_time(sdp:lead_time:by_app{app=~'.*%s.*'}[%s])";
static final String LEAD_TIME_FOR_CHANGE_BY_APP_OFFSET = "avg_over_time(sdp:lead_time:by_app{app=~'.*%1$s.*'}[%2$s] offset %2$s)";
// It should be this, once https://github.com/dora-metrics/pelorus/issues/1088
// gets resolved
static final String LEAD_TIME_FOR_CHANGE_BY_APP_DATA = "sdp:lead_time:by_commit{app=~'.*%s.*'}[%s]";
// private final String LEAD_TIME_FOR_CHANGE_BY_APP_DATA =
// "(min_over_time(deploy_timestamp{app=~\".*%1$s.*\"}[%2$s]) -
// on(app,image_sha) group_left(commit) (max by (app, commit, image_sha)
// (max_over_time(commit_timestamp{app=~\".*%1$s.*\"}[%2$s]))))";

// static String general(String range) {
// return String.format(LEAD_TIME_FOR_CHANGE, range);
// }

// static String byApp(String range) {
// return "TODO";
// }
// }

// #region Deployment Freq
private final String DEPLOYMENT_FREQUENCY = "count (count_over_time (deploy_timestamp [%s]))";
private final String DEPLOYMENT_FREQUENCY_BY_APP = "count (count_over_time (deploy_timestamp{app=~'.*%s.*'}[%s]))";
private final String DEPLOYMENT_FREQUENCY_BY_APP_OFFSET = "count (count_over_time (deploy_timestamp{app=~'.*%1$s.*'}[%2$s] offset %2$s))";
private final String DEPLOYMENT_FREQUENCY_BY_APP_DATA = "deploy_timestamp{app=~'.*%s.*'}[%s]";
//#endregion
// #endregion

//#region Mean Time To Restore
private final String MEAN_TIME_TO_RESTORE_BY_APP = "avg(avg_over_time(sdp:time_to_restore:by_app{app=~\".*%s.*\"}[%s] @ %s))";
private final String MEAN_TIME_TO_RESTORE_BY_APP_OFFSET = "avg(avg_over_time(sdp:time_to_restore:by_app{app=~\".*%1$s.*\"}[%2$s] @ %3$s offset %2$s))";
private final String MEAN_TIME_TO_RESTORE_BY_APP_DATA = "sdp:time_to_restore:by_issue{app=~\".*%s.*\"}[%s] @ %s";
//#endregion
private static class MeanTimeToRestore {
static final String MEAN_TIME_TO_RESTORE_BY_APP = "avg(avg_over_time(sdp:time_to_restore:by_app{app=~\".*%s.*\"}[%s] @ %s))";
static final String MEAN_TIME_TO_RESTORE_BY_APP_OFFSET = "avg(avg_over_time(sdp:time_to_restore:by_app{app=~\".*%1$s.*\"}[%2$s] @ %3$s offset %2$s))";
static final String MEAN_TIME_TO_RESTORE_BY_APP_DATA = "sdp:time_to_restore:by_issue{app=~\".*%s.*\"}[%s] @ %s";

//#region Change Failure Rate
static String byApp(AppRangeAt args) {
return MEAN_TIME_TO_RESTORE_BY_APP.formatted(args.app, args.range, args.at);
}

static String byAppOffset(AppRangeAt args) {
return MEAN_TIME_TO_RESTORE_BY_APP_OFFSET.formatted(args.app, args.range, args.at);
}

static String byAppData(AppRangeAt args) {
return MEAN_TIME_TO_RESTORE_BY_APP_DATA.formatteD(args.app, args.range, args.at);
}
}

// #region Change Failure Rate
private final String CHANGE_FAILURE_RATE = "(count by (app) (count_over_time(failure_creation_timestamp{app!=\"unknown\"}[%1$s]) or sdp:lead_time:by_app * 0) / count_over_time(sdp:lead_time:by_app [%1$s]))";
private final String CHANGE_FAILURE_RATE_BY_APP = "(count(count_over_time(failure_creation_timestamp{app=~\".*%1$s.*\"}[%2$s])) or sdp:lead_time:by_app * 0) / sum(count_over_time(sdp:lead_time:by_app{app=~\".*%1$s.*\"} [%2$s]))";
private final String CHANGE_FAILURE_RATE_BY_APP_OFFSET = "(count(count_over_time(failure_creation_timestamp{app=~\".*%1$s.*\"}[%2$s] offset %2$s)) or sdp:lead_time:by_app * 0) / sum(count_over_time(sdp:lead_time:by_app{app=~\".*%1$s.*\"} [%2$s] offset %2$s))";
//#endregion

// #endregion

private static final Logger LOG = Logger.getLogger(SoftwareDeliveryPerformanceApi.class);

Expand All @@ -80,14 +120,14 @@ private HTTPQueryResult logAndQuery(String queryName, String query) {
LOG.debugf("%s query: %s", queryName, query);
return queryService.runQuery(query);
}

@GET
@Path("/apps")
@Produces(MediaType.APPLICATION_JSON)
public List<App> getApps(@QueryParam("range") String range) {
HTTPQueryResult results = queryService.runQuery(String.format(APPS_LIST, range));
List<App> list = new ArrayList<App>();
for (QueryResult qr: results.data().result()) {
for (QueryResult qr : results.data().result()) {
App app = new App(qr.metric().app.replace("/", ""));
list.add(app);
}
Expand All @@ -107,9 +147,11 @@ public HTTPQueryResult queryLeadTimeforChange(@QueryParam("range") String range)
@Produces(MediaType.APPLICATION_JSON)
public LeadTime queryLeadTimeforChangeByApp(String app, @QueryParam("range") String range) {
HTTPQueryResult results = queryService.runQuery(String.format(LEAD_TIME_FOR_CHANGE_BY_APP, app, range));
HTTPQueryResult offset = queryService.runQuery(String.format(LEAD_TIME_FOR_CHANGE_BY_APP_OFFSET, app, range));
HTTPQueryResult offset = queryService
.runQuery(String.format(LEAD_TIME_FOR_CHANGE_BY_APP_OFFSET, app, range));
try {
return new LeadTime(results.data().result().get(0).value().value(), offset.data().result().get(0).value().value());
return new LeadTime(results.data().result().get(0).value().value(),
offset.data().result().get(0).value().value());
} catch (IndexOutOfBoundsException e) {
Double current = 0.0;
Double previous = 0.0;
Expand All @@ -127,15 +169,17 @@ public LeadTime queryLeadTimeforChangeByApp(String app, @QueryParam("range") Str
@Path("/lead_time_for_change/{app}/data")
@Produces(MediaType.APPLICATION_JSON)
public List<LeadTimeData> queryLeadTimeforChangeDataByApp(String app, @QueryParam("range") String range) {
HTTPQueryResult results = queryService.runQuery(String.format(LEAD_TIME_FOR_CHANGE_BY_APP_DATA, app, range));
List<LeadTimeData> leadTimeData= new ArrayList<LeadTimeData>();
for (QueryResult qr: results.data().result()) {
LeadTimeData data = new LeadTimeData(qr.metric().commit, qr.metric().image_sha, qr.values().get(0).timestamp(), qr.values().get(0).value());
HTTPQueryResult results = queryService
.runQuery(String.format(LEAD_TIME_FOR_CHANGE_BY_APP_DATA, app, range));
List<LeadTimeData> leadTimeData = new ArrayList<LeadTimeData>();
for (QueryResult qr : results.data().result()) {
LeadTimeData data = new LeadTimeData(qr.metric().commit, qr.metric().image_sha,
qr.values().get(0).timestamp(), qr.values().get(0).value());
leadTimeData.add(data);
}
return leadTimeData;
}

@GET
@Path("/deployment_frequency")
@Produces(MediaType.APPLICATION_JSON)
Expand All @@ -149,9 +193,11 @@ public HTTPQueryResult queryDeploymentFrequency(@QueryParam("range") String rang
@Produces(MediaType.APPLICATION_JSON)
public DeploymentFrequency queryDeploymentFrequencyByApp(String app, @QueryParam("range") String range) {
HTTPQueryResult results = queryService.runQuery(String.format(DEPLOYMENT_FREQUENCY_BY_APP, app, range));
HTTPQueryResult offset = queryService.runQuery(String.format(DEPLOYMENT_FREQUENCY_BY_APP_OFFSET, app, range));
try{
return new DeploymentFrequency(results.data().result().get(0).value().value(), offset.data().result().get(0).value().value());
HTTPQueryResult offset = queryService
.runQuery(String.format(DEPLOYMENT_FREQUENCY_BY_APP_OFFSET, app, range));
try {
return new DeploymentFrequency(results.data().result().get(0).value().value(),
offset.data().result().get(0).value().value());
} catch (IndexOutOfBoundsException e) {
Double current = 0.0;
Double previous = 0.0;
Expand All @@ -168,20 +214,24 @@ public DeploymentFrequency queryDeploymentFrequencyByApp(String app, @QueryParam
@GET
@Path("/deployment_frequency/{app}/data")
@Produces(MediaType.APPLICATION_JSON)
public List<DeploymentFrequencyData> queryDeploymentFrequencyDataByApp(String app, @QueryParam("range") String range) {
HTTPQueryResult results = queryService.runQuery(String.format(DEPLOYMENT_FREQUENCY_BY_APP_DATA, app, range));
List<DeploymentFrequencyData> deployFreqData= new ArrayList<DeploymentFrequencyData>();
for (QueryResult qr: results.data().result()) {
public List<DeploymentFrequencyData> queryDeploymentFrequencyDataByApp(String app,
@QueryParam("range") String range) {
HTTPQueryResult results = queryService
.runQuery(String.format(DEPLOYMENT_FREQUENCY_BY_APP_DATA, app, range));
List<DeploymentFrequencyData> deployFreqData = new ArrayList<DeploymentFrequencyData>();
for (QueryResult qr : results.data().result()) {
List<Value> lv = qr.values();
// Sort the deploy_timestamp metrics so that the earliest one is at the top of the list
// Sort the deploy_timestamp metrics so that the earliest one is at the top of
// the list
Collections.sort(lv, new Comparator<Value>() {
@Override
public int compare(Value v1, Value v2) {
return v1.timestamp().compareTo(v2.timestamp());
return v1.timestamp().compareTo(v2.timestamp());
}
});
// return only the first occurence of a deployment for a given image_sha
DeploymentFrequencyData data = new DeploymentFrequencyData(qr.metric().image_sha, lv.get(0).timestamp());
DeploymentFrequencyData data = new DeploymentFrequencyData(qr.metric().image_sha,
lv.get(0).timestamp());
deployFreqData.add(data);
}
return deployFreqData;
Expand All @@ -190,11 +240,17 @@ public int compare(Value v1, Value v2) {
@GET
@Path("/mean_time_to_restore/{app}")
@Produces(MediaType.APPLICATION_JSON)
public MeanTimeToRestore queryMeanTimeToRestoreByApp(String app, @QueryParam("range") String range, @QueryParam("start") String start) {
HTTPQueryResult results = logAndQuery("MEAN_TIME_TO_RESTORE_BY_APP", String.format(MEAN_TIME_TO_RESTORE_BY_APP, app, range, start));
HTTPQueryResult offset = logAndQuery("MEAN_TIME_TO_RESTORE_BY_APP_OFFSET", String.format(MEAN_TIME_TO_RESTORE_BY_APP_OFFSET, app, range, start));
try{
return new MeanTimeToRestore(results.data().result().get(0).value().value(),offset.data().result().get(0).value().value());
public MeanTimeToRestore queryMeanTimeToRestoreByApp(String app, @QueryParam("range") String range,
@QueryParam("start") String start) {
final AppRangeAt queryArgs = AppRangeAt.builder().app(app).range(range).at(start).build();

HTTPQueryResult results = logAndQuery("MEAN_TIME_TO_RESTORE_BY_APP",
MeanTimeToRestore.byApp(queryArgs));
HTTPQueryResult offset = logAndQuery("MEAN_TIME_TO_RESTORE_BY_APP_OFFSET",
MeanTimeToRestore.byAppOffset(queryArgs));
try {
return new MeanTimeToRestore(results.data().result().get(0).value().value(),
offset.data().result().get(0).value().value());
} catch (IndexOutOfBoundsException e) {
Double current = 0.0;
Double previous = 0.0;
Expand All @@ -211,25 +267,29 @@ public MeanTimeToRestore queryMeanTimeToRestoreByApp(String app, @QueryParam("ra
@GET
@Path("/mean_time_to_restore/{app}/data")
@Produces(MediaType.APPLICATION_JSON)
public List<MeanTimeToRestoreData> queryLeadMeanTimeToRestoreDataByApp(String app, @QueryParam("range") String range, @QueryParam("start") String start) {
HTTPQueryResult results = logAndQuery("MEAN_TIME_TO_RESTORE_BY_APP_DATA", String.format(MEAN_TIME_TO_RESTORE_BY_APP_DATA, app, range, start));
List<MeanTimeToRestoreData> mttrData= new ArrayList<MeanTimeToRestoreData>();
for (QueryResult qr: results.data().result()) {
MeanTimeToRestoreData data = new MeanTimeToRestoreData(qr.metric().issue_number, qr.values().get(0).value());
public List<MeanTimeToRestoreData> queryLeadMeanTimeToRestoreDataByApp(String app,
@QueryParam("range") String range, @QueryParam("start") String start) {
HTTPQueryResult results = logAndQuery("MEAN_TIME_TO_RESTORE_BY_APP_DATA",
MeanTimeToRestore.byAppData(AppRangeAt.builder().app(app).range(range).at(start).build()));
List<MeanTimeToRestoreData> mttrData = new ArrayList<MeanTimeToRestoreData>();
for (QueryResult qr : results.data().result()) {
MeanTimeToRestoreData data = new MeanTimeToRestoreData(qr.metric().issue_number,
qr.values().get(0).value());
mttrData.add(data);
}
return mttrData;
}


@GET
@Path("/change_failure_rate/{app}")
@Produces(MediaType.APPLICATION_JSON)
public ChangeFailureRate queryChangeFailureRateByApp(String app, @QueryParam("range") String range) {
HTTPQueryResult results = queryService.runQuery(String.format(CHANGE_FAILURE_RATE_BY_APP, app, range));
HTTPQueryResult offset = queryService.runQuery(String.format(CHANGE_FAILURE_RATE_BY_APP_OFFSET, app, range));
HTTPQueryResult offset = queryService
.runQuery(String.format(CHANGE_FAILURE_RATE_BY_APP_OFFSET, app, range));
try {
return new ChangeFailureRate(results.data().result().get(0).value().value(),offset.data().result().get(0).value().value());
return new ChangeFailureRate(results.data().result().get(0).value().value(),
offset.data().result().get(0).value().value());
} catch (IndexOutOfBoundsException e) {
Double current = 0.0;
Double previous = 0.0;
Expand Down

0 comments on commit 5720027

Please sign in to comment.