@Get
    @Post
    @Path("/api/v1/series")
    public HttpResponse series(
        @Param("match[]") String match,
        @Param("start") String start,
        @Param("end") String end,
        @Param("limit") Optional<Integer> limit) throws IOException {
        long startTS = formatTimestamp2Millis(start);
        long endTS = formatTimestamp2Millis(end);
        Duration duration = DurationUtils.timestamp2Duration(startTS, endTS);
        SeriesQueryRsp response = new SeriesQueryRsp();
        MatcherSetResult parseResult;
        try {
            parseResult = getMatcherSetResult(match);
        } catch (ParseCancellationException e) {
            response.setStatus(ResultStatus.ERROR);
            response.setErrorType(ErrorType.BAD_DATA);
            response.setError(e.getMessage());
            return jsonResponse(response);
        }
        String metricName = parseResult.getMetricName();
        Optional<ValueColumnMetadata.ValueColumn> valueColumn = ValueColumnMetadata.INSTANCE.readValueColumnDefinition(
            metricName);
        int limitNum = getLimitNum(limit, parseResult);
        if (valueColumn.isPresent()) {
            ValueColumnMetadata.ValueColumn metaData = valueColumn.get();
            Scope scope = Scope.Finder.valueOf(metaData.getScopeId());
            Column.ValueDataType dataType = metaData.getDataType();
            response.getData().add(buildMetaMetricInfo(metricName, scope, dataType));
        } else {
            try {
                String layer = parseResult.getLabelMap().get(LabelName.LAYER.getLabel());
                if (Objects.equals(metricName, ServiceTraffic.INDEX_NAME)) {
                    queryServiceTraffic(parseResult, layer, limitNum).forEach(service -> {
                        response.getData().add(buildMetricInfoFromTraffic(metricName, service));
                    });
                } else if (Objects.equals(metricName, InstanceTraffic.INDEX_NAME)) {
                    String serviceName = parseResult.getLabelMap().get(LabelName.SERVICE.getLabel());
                    queryInstanceTraffic(parseResult, duration, layer, serviceName, limitNum).forEach(instance -> {
                        response.getData().add(buildMetricInfoFromTraffic(metricName, instance));
                    });
                } else if (Objects.equals(metricName, EndpointTraffic.INDEX_NAME)) {
                    String serviceName = parseResult.getLabelMap().get(LabelName.SERVICE.getLabel());
                    String keyword = parseResult.getLabelMap().getOrDefault(LabelName.KEYWORD.getLabel(), "");
                    queryEndpointTraffic(parseResult, duration, layer, serviceName, keyword, limitNum).forEach(
                        endpoint -> {
                            response.getData().add(buildMetricInfoFromTraffic(metricName, endpoint));
                        });
                }
            } catch (IllegalExpressionException e) {
                response.setStatus(ResultStatus.ERROR);
                response.setErrorType(ErrorType.BAD_DATA);
                response.setError(e.getMessage());
            }
        }

        response.setStatus(ResultStatus.SUCCESS);
        return jsonResponse(response);
    }
If match[] is not present, return general labels values.
If end time is not present, use current time as default end time.
The start param will be ignored, reserve this param to keep consistent with API protocol.
Always query with day step.
oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/handler/PromQLApiHandler.java