Skip to content

Commit

Permalink
Merge pull request #267 from hawkular/counter-data-rest
Browse files Browse the repository at this point in the history
REST endpoints for reading/writing counter data points
  • Loading branch information
tsegismont committed Jun 22, 2015
2 parents 3427d25 + 8960414 commit 7db123b
Show file tree
Hide file tree
Showing 6 changed files with 405 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,17 @@
*/
package org.hawkular.metrics.api.jaxrs.handler;

import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import static org.hawkular.metrics.api.jaxrs.filter.TenantFilter.TENANT_HEADER_NAME;
import static org.hawkular.metrics.api.jaxrs.util.ApiUtils.emptyPayload;
import static org.hawkular.metrics.api.jaxrs.util.ApiUtils.requestToCounterDataPoints;
import static org.hawkular.metrics.api.jaxrs.util.ApiUtils.requestToCounters;
import static org.hawkular.metrics.core.api.MetricType.COUNTER;

import java.net.URI;
import java.util.List;

import javax.inject.Inject;
import javax.ws.rs.Consumes;
Expand All @@ -31,6 +36,7 @@
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.Suspended;
import javax.ws.rs.core.Context;
Expand All @@ -44,11 +50,16 @@
import com.wordnik.swagger.annotations.ApiResponses;
import org.hawkular.metrics.api.jaxrs.ApiError;
import org.hawkular.metrics.api.jaxrs.handler.observer.MetricCreatedObserver;
import org.hawkular.metrics.api.jaxrs.handler.observer.ResultSetObserver;
import org.hawkular.metrics.api.jaxrs.model.Counter;
import org.hawkular.metrics.api.jaxrs.model.CounterDataPoint;
import org.hawkular.metrics.api.jaxrs.request.MetricDefinition;
import org.hawkular.metrics.api.jaxrs.util.ApiUtils;
import org.hawkular.metrics.core.api.Metric;
import org.hawkular.metrics.core.api.MetricId;
import org.hawkular.metrics.core.api.MetricsService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;

/**
Expand All @@ -61,6 +72,11 @@
@Api(value = "", description = "Counter metrics interface. A counter is a metric whose value are monotonically " +
"increasing or decreasing.")
public class CounterHandler {

private static Logger logger = LoggerFactory.getLogger(CounterHandler.class);

private static final long EIGHT_HOURS = MILLISECONDS.convert(8, HOURS);

@Inject
private MetricsService metricsService;

Expand Down Expand Up @@ -112,4 +128,79 @@ public void getCounter(@Suspended final AsyncResponse asyncResponse, @PathParam(
.subscribe(asyncResponse::resume, t -> asyncResponse.resume(ApiUtils.serverError(t)));
}

@POST
@Path("/data")
@ApiOperation(value = "Add data points for multiple counters")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Adding data points succeeded."),
@ApiResponse(code = 400, message = "Missing or invalid payload", response = ApiError.class),
@ApiResponse(code = 500, message = "Unexpected error happened while storing the data points",
response = ApiError.class) })
public void addData(@Suspended final AsyncResponse asyncResponse,
@ApiParam(value = "List of metrics", required = true) List<Counter> counters) {

if (counters.isEmpty()) {
asyncResponse.resume(emptyPayload());
} else {
Observable<Metric<Long>> metrics = requestToCounters(tenantId, counters);
Observable<Void> observable = metricsService.addCounterData((metrics));
observable.subscribe(new ResultSetObserver(asyncResponse));
}
}

@POST
@Path("/{id}/data")
@ApiOperation(value = "Add data for a single counter")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Adding data succeeded."),
@ApiResponse(code = 400, message = "Missing or invalid payload", response = ApiError.class),
@ApiResponse(code = 500, message = "Unexpected error happened while storing the data",
response = ApiError.class), })
public void addData(
@Suspended final AsyncResponse asyncResponse,
@PathParam("id") String id,
@ApiParam(value = "List of data points containing timestamp and value", required = true)
List<CounterDataPoint> data) {

if (data.isEmpty()) {
asyncResponse.resume(emptyPayload());
} else {
Metric<Long> metric = new Metric<>(tenantId, COUNTER, new MetricId(id), requestToCounterDataPoints(data));
Observable<Void> observable = metricsService.addCounterData(Observable.just(metric));
observable.subscribe(new ResultSetObserver(asyncResponse));
}
}

@GET
@Path("/{id}/data")
@ApiOperation(value = "Retrieve counter data points.", response = List.class)
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successfully fetched metric data."),
@ApiResponse(code = 204, message = "No metric data was found."),
@ApiResponse(code = 400, message = "start or end parameter is invalid.",
response = ApiError.class),
@ApiResponse(code = 500, message = "Unexpected error occurred while fetching metric data.",
response = ApiError.class) })
public void findCounterData(
@Suspended AsyncResponse asyncResponse,
@PathParam("id") String id,
@ApiParam(value = "Defaults to now - 8 hours") @QueryParam("start") final Long start,
@ApiParam(value = "Defaults to now") @QueryParam("end") final Long end) {

long now = System.currentTimeMillis();
long startTime = start == null ? now - EIGHT_HOURS : start;
long endTime = end == null ? now : end;

metricsService.findCounterData(tenantId, new MetricId(id), startTime, endTime)
.map(CounterDataPoint::new)
.toList()
.map(ApiUtils::collectionToResponse)
.subscribe(
asyncResponse::resume,
t -> {
logger.warn("Failed to fetch counter data", t);
ApiUtils.serverError(t);
});
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright 2014-2015 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.hawkular.metrics.api.jaxrs.model;

import static java.util.Collections.unmodifiableList;

import java.util.List;
import java.util.Objects;

import com.fasterxml.jackson.annotation.JsonProperty;

/**
* A container for data points for a single counter metric.
*
* @author jsanda
*/
public class Counter {

@JsonProperty
private String id;

@JsonProperty
private List<CounterDataPoint> data;

public String getId() {
return id;
}

public List<CounterDataPoint> getData() {
return unmodifiableList(data);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Counter gauge = (Counter) o;
return Objects.equals(id, gauge.id) &&
Objects.equals(data, gauge.data);
}

@Override
public int hashCode() {
return Objects.hash(id, data);
}

@Override
public String toString() {
return com.google.common.base.Objects.toStringHelper(this)
.add("id", id)
.add("data", data)
.toString();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright 2014-2015 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.hawkular.metrics.api.jaxrs.model;

import java.util.Collections;
import java.util.Map;
import java.util.Objects;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.ImmutableMap;
import com.wordnik.swagger.annotations.ApiModel;
import org.hawkular.metrics.core.api.DataPoint;

/**
* @author jsanda
*/
@ApiModel(description = "A timestamp and a value where the value is interpreted as a signed 64 bit integer")
public class CounterDataPoint {

@JsonProperty
private long timestamp;

@JsonProperty
private Long value;

@JsonProperty
private Map<String, String> tags = Collections.emptyMap();

/**
* Used by JAX-RS/Jackson to deserialize HTTP request data
*/
private CounterDataPoint() {
}

/**
* Used to prepared data for serialization into the HTTP response
*
* @param dataPoint
*/
public CounterDataPoint(DataPoint<Long> dataPoint) {
timestamp = dataPoint.getTimestamp();
value = dataPoint.getValue();
tags = dataPoint.getTags();
}

public Long getValue() {
return value;
}

public long getTimestamp() {
return timestamp;
}

public Map<String, String> getTags() {
return ImmutableMap.copyOf(tags);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CounterDataPoint that = (CounterDataPoint) o;
// TODO should tags be included in equals?
return Objects.equals(timestamp, that.timestamp) &&
Objects.equals(value, that.value);
}

@Override
public int hashCode() {
// TODO should tags be included?
return Objects.hash(timestamp, value);
}

@Override
public String toString() {
return com.google.common.base.Objects.toStringHelper(this)
.add("timestamp", timestamp)
.add("value", value)
.add("tags", tags)
.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,6 @@ public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
GaugeDataPoint that = (GaugeDataPoint) o;
// TODO should tags be included in equals?
// return Objects.equals(timestamp, that.timestamp) &&
// Objects.equals(value, that.value) &&
// Objects.equals(tags, that.tags);
return Objects.equals(timestamp, that.timestamp) &&
Objects.equals(value, that.value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package org.hawkular.metrics.api.jaxrs.util;

import static java.util.stream.Collectors.toList;

import static org.hawkular.metrics.core.api.MetricType.COUNTER;
import static org.hawkular.metrics.core.api.MetricType.GAUGE;

import java.util.Collection;
Expand All @@ -28,21 +28,21 @@
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.core.Response;

import com.google.common.base.Function;
import com.google.common.base.Throwables;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import org.hawkular.metrics.api.jaxrs.ApiError;
import org.hawkular.metrics.api.jaxrs.model.AvailabilityDataPoint;
import org.hawkular.metrics.api.jaxrs.model.Counter;
import org.hawkular.metrics.api.jaxrs.model.CounterDataPoint;
import org.hawkular.metrics.api.jaxrs.model.Gauge;
import org.hawkular.metrics.api.jaxrs.model.GaugeDataPoint;
import org.hawkular.metrics.core.api.AvailabilityType;
import org.hawkular.metrics.core.api.DataPoint;
import org.hawkular.metrics.core.api.Metric;
import org.hawkular.metrics.core.api.MetricId;

import com.google.common.base.Function;
import com.google.common.base.Throwables;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;

import rx.Observable;

/**
Expand Down Expand Up @@ -86,6 +86,17 @@ public static Observable<Metric<Double>> requestToGauges(String tenantId, List<G
new Metric<>(tenantId, GAUGE, new MetricId(g.getId()), requestToGaugeDataPoints(g.getData())));
}

public static Observable<Metric<Long>> requestToCounters(String tenantId, List<Counter> counters) {
return Observable.from(counters).map(c ->
new Metric<>(tenantId, COUNTER, new MetricId(c.getId()), requestToCounterDataPoints(c.getData())));
}

public static List<DataPoint<Long>> requestToCounterDataPoints(List<CounterDataPoint> dataPoints) {
return dataPoints.stream()
.map(p -> new DataPoint<>(p.getTimestamp(), p.getValue(), p.getTags()))
.collect(toList());
}

// TODO We probably want to return an Observable here
public static List<DataPoint<AvailabilityType>> requestToAvailabilityDataPoints(
List<AvailabilityDataPoint> dataPoints) {
Expand Down

0 comments on commit 7db123b

Please sign in to comment.