/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.skywalking.e2e.controller; import com.google.protobuf.util.JsonFormat; import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.DocumentContext; import com.jayway.jsonpath.EvaluationListener; import com.jayway.jsonpath.JsonPath; import com.jayway.jsonpath.Option; import io.grpc.ManagedChannel; import io.grpc.internal.DnsNameResolverProvider; import io.grpc.netty.NettyChannelBuilder; import io.grpc.stub.StreamObserver; import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest; import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceResponse; import io.opentelemetry.proto.collector.metrics.v1.MetricsServiceGrpc; import java.io.File; import java.io.IOException; import lombok.extern.slf4j.Slf4j; import org.apache.skywalking.e2e.E2EConfiguration; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @Slf4j @RestController @RequestMapping("/otel-metrics") public class OtelMetricsSender { private static final int MAX_INBOUND_MESSAGE_SIZE = 1024 * 1024 * 50; private final MetricsServiceGrpc.MetricsServiceStub metricsServiceStub; private final String otelMetricsDataPath; public OtelMetricsSender(final E2EConfiguration configuration) { final ManagedChannel channel = NettyChannelBuilder.forAddress( configuration.getOapHost(), Integer.parseInt(configuration.getOapGrpcPort())) .nameResolverFactory(new DnsNameResolverProvider()) .maxInboundMessageSize(MAX_INBOUND_MESSAGE_SIZE) .usePlaintext() .build(); this.metricsServiceStub = MetricsServiceGrpc.newStub(channel); this.otelMetricsDataPath = configuration.getOtelMetricsDataPath(); } /** * Send otel metrics data that base on JSON formatted file to OAP * * This method will detect files whose name end with '.json' in the directory specified by * `org.apache.skywalking.e2e.E2EConfiguration#otelMetricsDataPath`, and send the data in the file to OAP. Also * rewrite field which path is '$..startTimeUnixNano', '$..timeUnixNano' to current time in nano */ @GetMapping("send") public String sendMetricsByTemplate() throws IOException { File otelData = new File(this.otelMetricsDataPath); if (!otelData.exists() || !otelData.isDirectory()) { String msg = "The path must be a folder : " + this.otelMetricsDataPath; log.error(msg); return msg; } final File[] files = findJSONFiles(otelData); if (files.length == 0) { String msg = "The folder doesn't contain any json file : " + this.otelMetricsDataPath; log.error(msg); return msg; } for (File file : files) { final ExportMetricsServiceRequest.Builder builder = ExportMetricsServiceRequest.newBuilder(); String jsonData = rewriteTimeField(file); JsonFormat.parser().merge(jsonData, builder); sendReq(builder); } return "ok"; } private String rewriteTimeField(File file) throws IOException { final long nanoTime = System.currentTimeMillis() * 1000000L; final Configuration configuration = Configuration.builder() .options(Option.SUPPRESS_EXCEPTIONS) .evaluationListener(found -> { log.info("rewrite json field: {}, {}->{}", found.path(), found.result(), nanoTime ); return EvaluationListener.EvaluationContinuation.CONTINUE; }).build(); final DocumentContext documentContext = JsonPath.using(configuration).parse(file); documentContext.set("$..timeUnixNano", nanoTime); documentContext.set("$..startTimeUnixNano", nanoTime); return documentContext.jsonString(); } private void sendReq(final ExportMetricsServiceRequest.Builder builder) { this.metricsServiceStub.export(builder.build(), new StreamObserver() { @Override public void onNext(final ExportMetricsServiceResponse exportMetricsServiceResponse) { } @Override public void onError(final Throwable throwable) { log.error("sendOtelMetrics by template error ", throwable); } @Override public void onCompleted() { } }); } private File[] findJSONFiles(final File otelData) { return otelData.listFiles((dir, name) -> name.endsWith(".json")); } }