protected void findRateDataPointsForMetrics(
      AsyncResponse asyncResponse, QueryRequest query, MetricType<? extends Number> type) {
    TimeRange timeRange = new TimeRange(query.getStart(), query.getEnd());
    if (!timeRange.isValid()) {
      asyncResponse.resume(badRequest(new ApiError(timeRange.getProblem())));
      return;
    }

    int limit;
    if (query.getLimit() == null) {
      limit = 0;
    } else {
      limit = query.getLimit();
    }
    Order order;
    if (query.getOrder() == null) {
      order = Order.defaultValue(limit, timeRange.getStart(), timeRange.getEnd());
    } else {
      order = Order.fromText(query.getOrder());
    }

    if (query.getIds().isEmpty()) {
      asyncResponse.resume(badRequest(new ApiError("Metric ids must be specified")));
      return;
    }

    List<MetricId<? extends Number>> metricIds =
        query.getIds().stream().map(id -> new MetricId<>(getTenant(), type, id)).collect(toList());
    Observable<NamedDataPoint<Double>> dataPoints =
        metricsService
            .findRateData(metricIds, timeRange.getStart(), timeRange.getEnd(), limit, order)
            .observeOn(Schedulers.io());

    HttpServletRequest request = ResteasyProviderFactory.getContextData(HttpServletRequest.class);
    HttpServletResponse response =
        ResteasyProviderFactory.getContextData(HttpServletResponse.class);

    if (type == GAUGE) {
      dataPoints.subscribe(new NamedDataPointObserver<>(request, response, mapper, GAUGE));
    } else if (type == COUNTER) {
      dataPoints.subscribe(new NamedDataPointObserver<>(request, response, mapper, COUNTER_RATE));
    } else {
      throw new IllegalArgumentException(
          type + " is not a supported metric type for rate data points");
    }
  }
  protected <T> void findRawDataPointsForMetrics(
      AsyncResponse asyncResponse, QueryRequest query, MetricType<T> type) {
    TimeRange timeRange = new TimeRange(query.getStart(), query.getEnd());
    if (!timeRange.isValid()) {
      asyncResponse.resume(badRequest(new ApiError(timeRange.getProblem())));
      return;
    }

    int limit;
    if (query.getLimit() == null) {
      limit = 0;
    } else {
      limit = query.getLimit();
    }
    Order order;
    if (query.getOrder() == null) {
      order = Order.defaultValue(limit, timeRange.getStart(), timeRange.getEnd());
    } else {
      order = Order.fromText(query.getOrder());
    }

    if (query.getIds().isEmpty()) {
      asyncResponse.resume(badRequest(new ApiError("Metric ids must be specified")));
      return;
    }

    List<MetricId<T>> metricIds =
        query.getIds().stream().map(id -> new MetricId<>(getTenant(), type, id)).collect(toList());
    Observable<NamedDataPoint<T>> dataPoints =
        metricsService
            .findDataPoints(metricIds, timeRange.getStart(), timeRange.getEnd(), limit, order)
            .observeOn(Schedulers.io());

    HttpServletRequest request = ResteasyProviderFactory.getContextData(HttpServletRequest.class);
    HttpServletResponse response =
        ResteasyProviderFactory.getContextData(HttpServletResponse.class);

    dataPoints.subscribe(new NamedDataPointObserver<>(request, response, mapper, type));
  }