/** * 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.apache.aurora.common.net.http.handlers; import java.util.List; import javax.annotation.Nullable; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.base.Splitter; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.gson.Gson; import com.google.gson.JsonObject; import com.google.inject.Inject; import org.apache.aurora.common.collections.Iterables2; import org.apache.aurora.common.stats.TimeSeries; import org.apache.aurora.common.stats.TimeSeriesRepository; /** * A servlet that provides time series data in JSON format. */ @Path("/graphdata/") public class TimeSeriesDataSource { @VisibleForTesting static final String TIME_METRIC = "time"; private static final String METRICS = "metrics"; private static final String SINCE = "since"; private final TimeSeriesRepository timeSeriesRepo; private final Gson gson = new Gson(); @Inject public TimeSeriesDataSource(TimeSeriesRepository timeSeriesRepo) { this.timeSeriesRepo = Preconditions.checkNotNull(timeSeriesRepo); } @VisibleForTesting String getResponse( @Nullable String metricsQuery, @Nullable String sinceQuery) throws MetricException { if (metricsQuery == null) { // Return metric listing. return gson.toJson(ImmutableList.copyOf(timeSeriesRepo.getAvailableSeries())); } List<Iterable<Number>> tsData = Lists.newArrayList(); tsData.add(timeSeriesRepo.getTimestamps()); // Ignore requests for "time" since it is implicitly returned. Iterable<String> names = Iterables.filter( Splitter.on(",").split(metricsQuery), Predicates.not(Predicates.equalTo(TIME_METRIC))); for (String metric : names) { TimeSeries series = timeSeriesRepo.get(metric); if (series == null) { JsonObject response = new JsonObject(); response.addProperty("error", "Unknown metric " + metric); throw new MetricException(gson.toJson(response)); } tsData.add(series.getSamples()); } final long since = Long.parseLong(Optional.fromNullable(sinceQuery).or("0")); Predicate<List<Number>> sinceFilter = next -> next.get(0).longValue() > since; ResponseStruct response = new ResponseStruct( ImmutableList.<String>builder().add(TIME_METRIC).addAll(names).build(), FluentIterable.from(Iterables2.zip(tsData, 0)).filter(sinceFilter).toList()); // TODO(wfarner): Let the jax-rs provider handle serialization. return gson.toJson(response); } @GET @Produces(MediaType.APPLICATION_JSON) public String getData( @QueryParam(METRICS) String metrics, @QueryParam(SINCE) String since) throws MetricException { return getResponse(metrics, since); } @VisibleForTesting static class ResponseStruct { // Fields must be non-final for deserialization. List<String> names; List<List<Number>> data; ResponseStruct(List<String> names, List<List<Number>> data) { this.names = names; this.data = data; } } @VisibleForTesting static class MetricException extends Exception { MetricException(String message) { super(message); } } }