From 2caa6dc8d5b2b4aa504017d5771297e40f140644 Mon Sep 17 00:00:00 2001 From: DEBAJIT DAS <85024550+DebajitDas@users.noreply.github.com> Date: Mon, 11 Jul 2022 20:54:39 +0530 Subject: [PATCH] Currency Service using C++ (#189) * Saving incremental changes * Saved the draft * Added convert function * Added some more changes * Updated more changes * More changes * Removed js code * Final changes * Currency Service in C++ * Cleaned up license * Removed trailing space * Incorporated review comments * Updated sanity failure * Resolved md error * Resolved md error Co-authored-by: Austin Parker Co-authored-by: Carter Socha <43380952+cartersocha@users.noreply.github.com> --- CHANGELOG.md | 2 + docker-compose.yml | 3 + src/currencyservice/CMakeLists.txt | 73 +++++++ src/currencyservice/Dockerfile | 103 +++++----- src/currencyservice/README.md | 33 +++- src/currencyservice/client.js | 65 ------ src/currencyservice/package.json | 25 --- src/currencyservice/server.js | 147 -------------- src/currencyservice/src/client.cpp | 151 ++++++++++++++ src/currencyservice/src/server.cpp | 252 ++++++++++++++++++++++++ src/currencyservice/src/tracer_common.h | 109 ++++++++++ src/currencyservice/tracing.js | 10 - 12 files changed, 678 insertions(+), 295 deletions(-) create mode 100644 src/currencyservice/CMakeLists.txt delete mode 100644 src/currencyservice/client.js delete mode 100644 src/currencyservice/package.json delete mode 100644 src/currencyservice/server.js create mode 100644 src/currencyservice/src/client.cpp create mode 100644 src/currencyservice/src/server.cpp create mode 100644 src/currencyservice/src/tracer_common.h delete mode 100644 src/currencyservice/tracing.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 0884c0df1e..fae3c67589 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,3 +19,5 @@ release. ([#82](https://github.com/open-telemetry/opentelemetry-demo-webstore/pull/82)) * Added feature flag service implementation ([#141](https://github.com/open-telemetry/opentelemetry-demo-webstore/pull/141)) +* Re-implemented currency service using C++ + ([#189](https://github.com/open-telemetry/opentelemetry-demo-webstore/pull/189)) diff --git a/docker-compose.yml b/docker-compose.yml index 99b6e5b222..b940f37e1a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -102,6 +102,9 @@ services: image: ${IMAGE_NAME}:${IMAGE_VERSION}-currencyservice build: context: ./src/currencyservice + args: + - GRPC_VERSION=1.46.0 + - OPENTELEMETRY_VERSION=1.4.0 ports: - "${CURRENCY_SERVICE_PORT}" environment: diff --git a/src/currencyservice/CMakeLists.txt b/src/currencyservice/CMakeLists.txt new file mode 100644 index 0000000000..d24357a4f8 --- /dev/null +++ b/src/currencyservice/CMakeLists.txt @@ -0,0 +1,73 @@ +cmake_minimum_required(VERSION 3.1) +project(currency-service) + +find_package(Protobuf) +find_package(gRPC) + +set(PROTO_PATH "${CMAKE_CURRENT_SOURCE_DIR}/proto") +set(GENERATED_PROTOBUF_PATH "${CMAKE_BINARY_DIR}/generated/proto") +set(GENERATED_HEALTH_PROTOBUF_PATH "${GENERATED_PROTOBUF_PATH}/grpc/health/v1") + +file(MAKE_DIRECTORY "${GENERATED_PROTOBUF_PATH}") + +set(DEMO_PROTO "${PROTO_PATH}/demo.proto") +set(DEMO_PB_CPP_FILE "${GENERATED_PROTOBUF_PATH}/demo.pb.cc") +set(DEMO_PB_H_FILE "${GENERATED_PROTOBUF_PATH}/demo.pb.h") +set(DEMO_GRPC_PB_CPP_FILE "${GENERATED_PROTOBUF_PATH}/demo.grpc.pb.cc") +set(DEMO_GRPC_PB_H_FILE "${GENERATED_PROTOBUF_PATH}/demo.grpc.pb.h") +set(HEALTH_PROTO "${PROTO_PATH}/grpc/health/v1/health.proto") +set(HEALTH_PB_CPP_FILE "${GENERATED_HEALTH_PROTOBUF_PATH}/health.pb.cc") +set(HEALTH_PB_H_FILE "${GENERATED_HEALTH_PROTOBUF_PATH}/health.pb.h") +set(HEALTH_GRPC_PB_CPP_FILE "${GENERATED_HEALTH_PROTOBUF_PATH}/health.grpc.pb.cc") +set(HEALTH_GRPC_PB_H_FILE "${GENERATED_HEALTH_PROTOBUF_PATH}/health.grpc.pb.h") + +foreach(IMPORT_DIR ${PROTOBUF_IMPORT_DIRS}) + list(APPEND PROTOBUF_INCLUDE_FLAGS "-I${IMPORT_DIR}") +endforeach() + +find_program(gRPC_CPP_PLUGIN_EXECUTABLE grpc_cpp_plugin) + +add_custom_command( + OUTPUT ${DEMO_PB_H_FILE} + ${DEMO_PB_CPP_FILE} + ${DEMO_GRPC_PB_CPP_FILE} + ${DEMO_GRPC_PB_H_FILE} + ${HEALTH_PB_H_FILE} + ${HEALTH_PB_CPP_FILE} + ${HEALTH_GRPC_PB_CPP_FILE} + ${HEALTH_GRPC_PB_H_FILE} + + COMMAND + ${PROTOBUF_PROTOC_EXECUTABLE} ARGS "--experimental_allow_proto3_optional" + "--proto_path=${PROTO_PATH}" ${PROTOBUF_INCLUDE_FLAGS} + "--cpp_out=${GENERATED_PROTOBUF_PATH}" + "--grpc_out=generate_mock_code=true:${GENERATED_PROTOBUF_PATH}" + --plugin=protoc-gen-grpc="${gRPC_CPP_PLUGIN_EXECUTABLE}" ${DEMO_PROTO} ${HEALTH_PROTO}) + +message(STATUS "gRPC_CPP_PLUGIN_EXECUTABLE=${gRPC_CPP_PLUGIN_EXECUTABLE}") + +add_library(demo-proto ${DEMO_PB_H_FILE} + ${DEMO_PB_CPP_FILE} + ${DEMO_GRPC_PB_CPP_FILE} + ${DEMO_GRPC_PB_H_FILE} + ${HEALTH_PB_H_FILE} + ${HEALTH_PB_CPP_FILE} + ${HEALTH_GRPC_PB_CPP_FILE} + ${HEALTH_GRPC_PB_H_FILE}) + +target_link_libraries(demo-proto gRPC::grpc++ protobuf::libprotobuf) +include_directories("${GENERATED_PROTOBUF_PATH}") + +add_executable(currencyservice src/server.cpp) +add_dependencies(currencyservice demo-proto) +target_link_libraries( + currencyservice demo-proto protobuf::libprotobuf + opentelemetry_resources opentelemetry_trace opentelemetry_common opentelemetry_exporter_otlp_grpc opentelemetry_proto opentelemetry_otlp_recordable gRPC::grpc++) + +add_executable(currencyclient src/client.cpp) +add_dependencies(currencyclient demo-proto) +target_link_libraries( + currencyclient demo-proto protobuf::libprotobuf gRPC::grpc++) + +install(TARGETS currencyservice DESTINATION bin) +install(TARGETS currencyclient DESTINATION bin) diff --git a/src/currencyservice/Dockerfile b/src/currencyservice/Dockerfile index 7b501f14bf..b6f7b42268 100644 --- a/src/currencyservice/Dockerfile +++ b/src/currencyservice/Dockerfile @@ -1,46 +1,57 @@ -# Copyright 2020 Google LLC -# -# 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. - -FROM node:16-alpine as base - -FROM base as builder - -# Some packages (e.g. @google-cloud/profiler) require additional -# deps for post-install scripts -RUN apk add --update --no-cache \ - python3 \ - make \ - g++ - -WORKDIR /usr/src/app - -COPY package*.json ./ - -RUN npm install --only=production - -FROM base - -RUN GRPC_HEALTH_PROBE_VERSION=v0.4.7 && \ - wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ - chmod +x /bin/grpc_health_probe - -WORKDIR /usr/src/app - -COPY --from=builder /usr/src/app/node_modules ./node_modules - -COPY . . - -EXPOSE 7000 - -ENTRYPOINT [ "node", "--require", "./tracing.js", "server.js" ] +FROM ubuntu:20.04 +ENV DEBIAN_FRONTEND noninteractive + +RUN apt-get -y update +RUN apt-get -y upgrade && apt-get -y dist-upgrade +RUN apt-get install -qq -y --ignore-missing \ + build-essential \ + git \ + make \ + pkg-config \ + protobuf-compiler \ + libprotobuf-dev \ + cmake + +# The following arguments would be passed from docker-compose.yml +ARG GRPC_VERSION +ARG OPENTELEMETRY_VERSION + +# Install GRPC +RUN git clone --shallow-submodules --depth 1 --recurse-submodules -b v${GRPC_VERSION} \ + https://github.com/grpc/grpc \ + && cd grpc \ + && mkdir -p cmake/build \ + && cd cmake/build \ + && cmake \ + -DgRPC_INSTALL=ON \ + -DgRPC_BUILD_TESTS=OFF \ + -DCMAKE_BUILD_TYPE=Release \ + -DgRPC_BUILD_GRPC_NODE_PLUGIN=OFF \ + -DgRPC_BUILD_GRPC_OBJECTIVE_C_PLUGIN=OFF \ + -DgRPC_BUILD_GRPC_PHP_PLUGIN=OFF \ + -DgRPC_BUILD_GRPC_PHP_PLUGIN=OFF \ + -DgRPC_BUILD_GRPC_PYTHON_PLUGIN=OFF \ + -DgRPC_BUILD_GRPC_RUBY_PLUGIN=OFF \ + ../.. \ + && make -j2 \ + && make install \ + && cd ../../.. \ + && rm -rf grpc + +# install opentelemetry +RUN git clone https://github.com/open-telemetry/opentelemetry-cpp \ + && cd opentelemetry-cpp/ \ + && git checkout tags/v${OPENTELEMETRY_VERSION} -b v${OPENTELEMETRY_VERSION} \ + && git submodule update --init --recursive \ + && mkdir build \ + && cd build \ + && cmake .. -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF -DWITH_OTLP=ON \ + && make -j install && cd ../.. && rm -rf opentelemetry-cpp + +COPY . /currencyservice + +RUN cd /currencyservice \ + && mkdir -p build && cd build \ + && cmake .. && make -j install + +ENTRYPOINT currencyservice ${PORT} ${OTEL_EXPORTER_OTLP_TRACES_ENDPOINT} ${OTEL_RESOURCE_ATTRIBUTES} diff --git a/src/currencyservice/README.md b/src/currencyservice/README.md index 1cb920bac8..0002bf080c 100644 --- a/src/currencyservice/README.md +++ b/src/currencyservice/README.md @@ -1,3 +1,32 @@ -# Read Me +# Currency Service -This is a placeholder +The Currency Service does the conversion from one currency to another. +It is a C++ based service. + +## Building docker image + +To build the currency service, run the following from root directory +of opentelemetry-demo-webstore + +```sh +docker-compose build currencyservice +``` + +## Run the service + +Execute the below command to run the service. + +```sh +docker-compose up currencyservice +``` + +## Run the client + +currencyclient is a sample client which sends some request to currency +service. To run the client, execute the below command. + +```sh +docker exec -it currencyclient 7000 +``` + +'7000' is port where currencyservice listens to. diff --git a/src/currencyservice/client.js b/src/currencyservice/client.js deleted file mode 100644 index ece6f26db6..0000000000 --- a/src/currencyservice/client.js +++ /dev/null @@ -1,65 +0,0 @@ -/* - * - * Copyright 2015 gRPC authors. - * - * 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. - * - */ - -const path = require('path'); -const grpc = require('grpc'); -const leftPad = require('left-pad'); -const pino = require('pino'); - -const PROTO_PATH = path.join(__dirname, './proto/demo.proto'); -const PORT = 7000; - -const shopProto = grpc.load(PROTO_PATH).hipstershop; -const client = new shopProto.CurrencyService(`localhost:${PORT}`, - grpc.credentials.createInsecure()); - -const logger = pino({ - name: 'currencyservice-client', - messageKey: 'message', - levelKey: 'severity', - useLevelLabels: true -}); - -const request = { - from: { - currency_code: 'CHF', - units: 300, - nanos: 0 - }, - to_code: 'EUR' -}; - -function _moneyToString (m) { - return `${m.units}.${m.nanos.toString().padStart(9,'0')} ${m.currency_code}`; -} - -client.getSupportedCurrencies({}, (err, response) => { - if (err) { - logger.error(`Error in getSupportedCurrencies: ${err}`); - } else { - logger.info(`Currency codes: ${response.currency_codes}`); - } -}); - -client.convert(request, (err, response) => { - if (err) { - logger.error(`Error in convert: ${err}`); - } else { - logger.log(`Convert: ${_moneyToString(request.from)} to ${_moneyToString(response)}`); - } -}); diff --git a/src/currencyservice/package.json b/src/currencyservice/package.json deleted file mode 100644 index b233b76645..0000000000 --- a/src/currencyservice/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "grpc-currency-service", - "version": "0.1.0", - "description": "A gRPC currency conversion microservice", - "repository": "https://github.com/GoogleCloudPlatform/microservices-demo", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "lint": "semistandard *.js" - }, - "license": "Apache-2.0", - "dependencies": { - "@grpc/grpc-js": "^1.5.7", - "@grpc/proto-loader": "^0.6.9", - "@opentelemetry/auto-instrumentations-node": "^0.27.3", - "@opentelemetry/exporter-trace-otlp-grpc": "^0.27.0", - "@opentelemetry/sdk-node": "^0.27.0", - "async": "^3.2.3", - "google-protobuf": "^3.19.4", - "pino": "^7.8.0", - "xml2js": "^0.4.23" - }, - "devDependencies": { - "semistandard": "^16.0.1" - } -} diff --git a/src/currencyservice/server.js b/src/currencyservice/server.js deleted file mode 100644 index c8382c876b..0000000000 --- a/src/currencyservice/server.js +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright 2018 Google LLC. - * - * 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. - */ - -const path = require('path'); -const grpc = require('@grpc/grpc-js'); -const pino = require('pino'); -const protoLoader = require('@grpc/proto-loader'); - -const MAIN_PROTO_PATH = path.join(__dirname, './proto/demo.proto'); -const HEALTH_PROTO_PATH = path.join(__dirname, './proto/grpc/health/v1/health.proto'); - -const PORT = process.env.PORT; - -const shopProto = _loadProto(MAIN_PROTO_PATH).hipstershop; -const healthProto = _loadProto(HEALTH_PROTO_PATH).grpc.health.v1; - -const logger = pino({ - name: 'currencyservice-server', - messageKey: 'message', - levelKey: 'severity', - useLevelLabels: true -}); - -/** - * Helper function that loads a protobuf file. - */ -function _loadProto (path) { - const packageDefinition = protoLoader.loadSync( - path, - { - keepCase: true, - longs: String, - enums: String, - defaults: true, - oneofs: true - } - ); - return grpc.loadPackageDefinition(packageDefinition); -} - -/** - * Helper function that gets currency data from a stored JSON file - * Uses public data from European Central Bank - */ -function _getCurrencyData (callback) { - const data = require('./data/currency_conversion.json'); - callback(data); -} - -/** - * Helper function that handles decimal/fractional carrying - */ -function _carry (amount) { - const fractionSize = Math.pow(10, 9); - amount.nanos += (amount.units % 1) * fractionSize; - amount.units = Math.floor(amount.units) + Math.floor(amount.nanos / fractionSize); - amount.nanos = amount.nanos % fractionSize; - return amount; -} - -/** - * Lists the supported currencies - */ -function getSupportedCurrencies (call, callback) { - logger.info('Getting supported currencies...'); - _getCurrencyData((data) => { - callback(null, {currency_codes: Object.keys(data)}); - }); -} - -/** - * Converts between currencies - */ -function convert (call, callback) { - try { - _getCurrencyData((data) => { - const request = call.request; - - // Convert: from_currency --> EUR - const from = request.from; - const euros = _carry({ - units: from.units / data[from.currency_code], - nanos: from.nanos / data[from.currency_code] - }); - - euros.nanos = Math.round(euros.nanos); - - // Convert: EUR --> to_currency - const result = _carry({ - units: euros.units * data[request.to_code], - nanos: euros.nanos * data[request.to_code] - }); - - result.units = Math.floor(result.units); - result.nanos = Math.floor(result.nanos); - result.currency_code = request.to_code; - - logger.info(`conversion request successful`); - callback(null, result); - }); - } catch (err) { - logger.error(`conversion request failed: ${err}`); - callback(err.message); - } -} - -/** - * Endpoint for health checks - */ -function check (call, callback) { - callback(null, { status: 'SERVING' }); -} - -/** - * Starts an RPC server that receives requests for the - * CurrencyConverter service at the sample server port - */ -function main () { - logger.info(`Starting gRPC server on port ${PORT}...`); - const server = new grpc.Server(); - server.addService(shopProto.CurrencyService.service, {getSupportedCurrencies, convert}); - server.addService(healthProto.Health.service, {check}); - - server.bindAsync( - `0.0.0.0:${PORT}`, - grpc.ServerCredentials.createInsecure(), - function() { - logger.info(`CurrencyService gRPC server started on port ${PORT}`); - server.start(); - }, - ); -} - -main(); diff --git a/src/currencyservice/src/client.cpp b/src/currencyservice/src/client.cpp new file mode 100644 index 0000000000..a4b3959e19 --- /dev/null +++ b/src/currencyservice/src/client.cpp @@ -0,0 +1,151 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace std; + +using hipstershop::CurrencyService; +using hipstershop::Empty; +using hipstershop::GetSupportedCurrenciesResponse; +using hipstershop::CurrencyConversionRequest; +using hipstershop::Money; + +using grpc::Status; +using grpc::ServerContext; +using grpc::ServerBuilder; +using grpc::Server; +using grpc::Channel; +using grpc::StubOptions; +using grpc::ClientContext; +using grpc::health::v1::Health; +using grpc::health::v1::HealthCheckRequest; +using grpc::health::v1::HealthCheckResponse; + +namespace +{ +class CurrencyClient +{ +public: + CurrencyClient(std::shared_ptr channel) + : stub_(CurrencyService::NewStub(channel, StubOptions{})) + , hc_stub_(grpc::health::v1::Health::NewStub(channel)) {} + + void GetSupportedCurrencies() + { + Empty request; + GetSupportedCurrenciesResponse response; + ClientContext context; + + supported_currencies.clear(); + Status status = stub_->GetSupportedCurrencies(&context, request, &response); + + std::cout << "Supported Currencies are: \n[ "; + for (int i = 0; i < response.currency_codes_size(); i++) { + std::cout << response.currency_codes(i) << " "; + supported_currencies.push_back(response.currency_codes(i)); + } + std::cout << "]" << std::endl; + } + + void Convert() + { + size_t max = supported_currencies.size(); + srand(time(0)); + uint32_t src_index = rand()%max; + uint32_t dst_index = rand()%max; + uint32_t unit = rand()%100; + uint32_t nano = rand()%100; + + CurrencyConversionRequest request; + Money *money = request.mutable_from(); + money->set_currency_code(supported_currencies[src_index]); + money->set_units(unit); + money->set_nanos(nano); + request.set_to_code(supported_currencies[dst_index]); + + std::cout << "Sending conversion request to convert from " + << unit << "." << nano << " " + << supported_currencies[src_index] + << " to " << supported_currencies[dst_index] << std::endl; + + Money response; + ClientContext context; + Status status = stub_->Convert(&context, request, &response); + + if (status.ok()) { + std::cout << unit << "." << nano << " " + << supported_currencies[src_index] + << " converted to " + << response.units() << "." + << response.nanos() << " " + << response.currency_code() << std::endl; + } else { + std::cout << "Currency Conversion failed \n"; + } + } + + bool CheckHealthStatus() + { + HealthCheckRequest request; + request.set_service("CurrencyService"); + HealthCheckResponse response; + ClientContext context; + Status s = hc_stub_->Check(&context, request, &response); + if (s.ok()) { + if (response.status() == grpc::health::v1::HealthCheckResponse::SERVING) { + std::cout << "CurrencyService is up and running" << std::endl; + return true; + } + } + std::cout << "CurrencyService unreachable" << std::endl; + return false; + } + +private: + std::unique_ptr stub_; + std::unique_ptr hc_stub_; + std::vector supported_currencies; +}; + +void RunClient(uint16_t port) +{ + CurrencyClient client( + grpc::CreateChannel + ("0.0.0.0:" + std::to_string(port), grpc::InsecureChannelCredentials())); + + if (client.CheckHealthStatus()) { + client.GetSupportedCurrencies(); + while (true) { + client.Convert(); + std::this_thread::sleep_for(std::chrono::milliseconds(2000)); + } + } +} +} + +int main(int argc, char **argv) { + constexpr uint16_t default_port = 8800; + uint16_t port; + if (argc > 1) + { + port = atoi(argv[1]); + } + else + { + port = default_port; + } + + RunClient(port); + return 0; +} diff --git a/src/currencyservice/src/server.cpp b/src/currencyservice/src/server.cpp new file mode 100644 index 0000000000..28f5200031 --- /dev/null +++ b/src/currencyservice/src/server.cpp @@ -0,0 +1,252 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include + +#include "opentelemetry/trace/context.h" +#include "opentelemetry/trace/experimental_semantic_conventions.h" +#include "opentelemetry/trace/span_context_kv_iterable_view.h" +#include "tracer_common.h" + +#include +#include +#include +#include + +using namespace std; + +using hipstershop::Empty; +using hipstershop::GetSupportedCurrenciesResponse; +using hipstershop::CurrencyConversionRequest; +using hipstershop::Money; + +using grpc::Status; +using grpc::ServerContext; +using grpc::ServerBuilder; +using grpc::Server; + +using Span = opentelemetry::trace::Span; +using SpanContext = opentelemetry::trace::SpanContext; +using namespace opentelemetry::trace; + +namespace context = opentelemetry::context; + +namespace +{ + std::unordered_map currency_conversion{ + {"EUR", 1.0}, + {"USD", 1.1305}, + {"JPY", 126.40}, + {"BGN", 1.9558}, + {"CZK", 25.592}, + {"DKK", 7.4609}, + {"GBP", 0.85970}, + {"HUF", 315.51}, + {"PLN", 4.2996}, + {"RON", 4.7463}, + {"SEK", 10.5375}, + {"CHF", 1.1360}, + {"ISK", 136.80}, + {"NOK", 9.8040}, + {"HRK", 7.4210}, + {"RUB", 74.4208}, + {"TRY", 6.1247}, + {"AUD", 1.6072}, + {"BRL", 4.2682}, + {"CAD", 1.5128}, + {"CNY", 7.5857}, + {"HKD", 8.8743}, + {"IDR", 15999.40}, + {"ILS", 4.0875}, + {"INR", 79.4320}, + {"KRW", 1275.05}, + {"MXN", 21.7999}, + {"MYR", 4.6289}, + {"NZD", 1.6679}, + {"PHP", 59.083}, + {"SGD", 1.5349}, + {"THB", 36.012}, + {"ZAR", 16.0583}, +}; + +class HealthServer final : public grpc::health::v1::Health::Service +{ + Status Check( + ServerContext* context, + const grpc::health::v1::HealthCheckRequest* request, + grpc::health::v1::HealthCheckResponse* response) override + { + response->set_status(grpc::health::v1::HealthCheckResponse::SERVING); + return Status::OK; + } +}; + +class CurrencyService final : public hipstershop::CurrencyService::Service +{ + Status GetSupportedCurrencies(ServerContext* context, + const Empty* request, + GetSupportedCurrenciesResponse* response) override + { + StartSpanOptions options; + options.kind = SpanKind::kServer; + GrpcServerCarrier carrier(context); + + auto prop = context::propagation::GlobalTextMapPropagator::GetGlobalPropagator(); + auto current_ctx = context::RuntimeContext::GetCurrent(); + auto new_context = prop->Extract(carrier, current_ctx); + options.parent = GetSpan(new_context)->GetContext(); + + std::string span_name = "CurrencyService/GetSupportedCurrencies"; + auto span = + get_tracer("currencyservice")->StartSpan(span_name, + {{OTEL_GET_TRACE_ATTR(AttrRpcSystem), "grpc"}, + {OTEL_GET_TRACE_ATTR(AttrRpcService), "CurrencyService"}, + {OTEL_GET_TRACE_ATTR(AttrRpcMethod), "GetSupportedCurrencies"}, + {OTEL_GET_TRACE_ATTR(AttrRpcGrpcStatusCode), 0}}, + options); + auto scope = get_tracer("currencyservice")->WithActiveSpan(span); + // Fetch and parse whatever HTTP headers we can from the gRPC request. + span->AddEvent("Processing supported currencies request"); + + for (auto &code : currency_conversion) { + response->add_currency_codes(code.first); + } + + span->AddEvent("Currencies fetched, response sent back"); + span->SetStatus(StatusCode::kOk); + // Make sure to end your spans! + span->End(); + + std::cout << __func__ << " successful" << std::endl; + return Status::OK; + } + + double getDouble(Money& money) { + auto units = money.units(); + auto nanos = money.nanos(); + + double decimal = 0.0; + while (nanos != 0) { + double t = (double)(nanos%10)/10; + nanos = nanos/10; + decimal = decimal/10 + t; + } + + return double(units) + decimal; + } + + void getUnitsAndNanos(Money& money, double value) { + long unit = (long)value; + double rem = value - unit; + long nano = rem * pow(10, 9); + money.set_units(unit); + money.set_nanos(nano); + } + + Status Convert(ServerContext* context, + const CurrencyConversionRequest* request, + Money* response) override + { + StartSpanOptions options; + options.kind = SpanKind::kServer; + GrpcServerCarrier carrier(context); + + auto prop = context::propagation::GlobalTextMapPropagator::GetGlobalPropagator(); + auto current_ctx = context::RuntimeContext::GetCurrent(); + auto new_context = prop->Extract(carrier, current_ctx); + options.parent = GetSpan(new_context)->GetContext(); + + std::string span_name = "CurrencyService/Convert"; + auto span = + get_tracer("currencyservice")->StartSpan(span_name, + {{OTEL_GET_TRACE_ATTR(AttrRpcSystem), "grpc"}, + {OTEL_GET_TRACE_ATTR(AttrRpcService), "CurrencyService"}, + {OTEL_GET_TRACE_ATTR(AttrRpcMethod), "Convert"}, + {OTEL_GET_TRACE_ATTR(AttrRpcGrpcStatusCode), 0}}, + options); + auto scope = get_tracer("currencyservice")->WithActiveSpan(span); + // Fetch and parse whatever HTTP headers we can from the gRPC request. + span->AddEvent("Processing currency conversion request"); + + try { + // Do the conversion work + Money from = request->from(); + string from_code = from.currency_code(); + double rate = currency_conversion[from_code]; + double one_euro = getDouble(from) / rate ; + + string to_code = request->to_code(); + double to_rate = currency_conversion[to_code]; + + double final = one_euro * to_rate; + getUnitsAndNanos(*response, final); + response->set_currency_code(to_code); + + // End the span + span->AddEvent("Conversion successful, response sent back"); + span->SetStatus(StatusCode::kOk); + std::cout << __func__ << " conversion successful" << std::endl; + span->End(); + return Status::OK; + + } catch(...) { + span->AddEvent("Conversion failed"); + span->SetStatus(StatusCode::kError); + std::cout << __func__ << " conversion failure" << std::endl; + span->End(); + return Status::CANCELLED; + } + return Status::OK; + } +}; + +void RunServer(uint16_t port) +{ + std::string address("0.0.0.0:" + std::to_string(port)); + CurrencyService currencyService; + HealthServer healthService; + ServerBuilder builder; + + builder.RegisterService(¤cyService); + builder.RegisterService(&healthService); + builder.AddListeningPort(address, grpc::InsecureServerCredentials()); + + std::unique_ptr server(builder.BuildAndStart()); + std::cout << "Currency Server listening on port: " << address << std::endl; + server->Wait(); + server->Shutdown(); +} +} + +int main(int argc, char **argv) { + + if (argc < 4) { + std::cout << "Usage: currencyservice "; + return 0; + } + + uint16_t port = atoi(argv[1]); + std::string endpoint = argv[2]; + std::string resourceAttr = argv[3]; + + std::cout << "Port: " << port + << "\n Collector Endpoint: " << endpoint + << "\n Resource Attributes: " << resourceAttr << "\n"; + + std::string resourceKey, resourceValue; + std::string delimiter{"="}; + size_t pos = 0; + while ((pos = resourceAttr.find(delimiter)) != std::string::npos) { + resourceKey = resourceAttr.substr(0, pos); + resourceAttr.erase(0, pos + delimiter.length()); + } + + std::cout << resourceKey << ": " << resourceAttr << std::endl; + initTracer(endpoint, resourceKey, resourceAttr); + RunServer(port); + + return 0; +} diff --git a/src/currencyservice/src/tracer_common.h b/src/currencyservice/src/tracer_common.h new file mode 100644 index 0000000000..b3c5caefa6 --- /dev/null +++ b/src/currencyservice/src/tracer_common.h @@ -0,0 +1,109 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once +#include "opentelemetry/exporters/ostream/span_exporter.h" +#include "opentelemetry/sdk/trace/simple_processor.h" +#include "opentelemetry/sdk/trace/tracer_provider.h" +#include "opentelemetry/sdk/resource/resource.h" +#include "opentelemetry/trace/provider.h" +#include "opentelemetry/exporters/otlp/otlp_grpc_exporter.h" + +#include "opentelemetry/context/propagation/global_propagator.h" +#include "opentelemetry/context/propagation/text_map_propagator.h" +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/trace/propagation/http_trace_context.h" + +#include +#include +#include +#include + +using grpc::ClientContext; +using grpc::ServerContext; + +namespace +{ +class GrpcClientCarrier : public opentelemetry::context::propagation::TextMapCarrier +{ +public: + GrpcClientCarrier(ClientContext *context) : context_(context) {} + GrpcClientCarrier() = default; + virtual opentelemetry::nostd::string_view Get( + opentelemetry::nostd::string_view key) const noexcept override + { + return ""; + } + + virtual void Set(opentelemetry::nostd::string_view key, + opentelemetry::nostd::string_view value) noexcept override + { + std::cout << " Client ::: Adding " << key << " " << value << "\n"; + context_->AddMetadata(key.data(), value.data()); + } + + ClientContext *context_; +}; + +class GrpcServerCarrier : public opentelemetry::context::propagation::TextMapCarrier +{ +public: + GrpcServerCarrier(ServerContext *context) : context_(context) {} + GrpcServerCarrier() = default; + virtual opentelemetry::nostd::string_view Get( + opentelemetry::nostd::string_view key) const noexcept override + { + auto it = context_->client_metadata().find(key.data()); + if (it != context_->client_metadata().end()) + { + return it->second.data(); + } + return ""; + } + + virtual void Set(opentelemetry::nostd::string_view key, + opentelemetry::nostd::string_view value) noexcept override + { + // Not required for server + } + + ServerContext *context_; +}; + +void initTracer(std::string endpoint, + std::string resourcekey = "service.name", + std::string resourceval = "currencyservice") +{ + opentelemetry::exporter::otlp::OtlpGrpcExporterOptions opts; + opts.endpoint = endpoint; + + auto exporter = std::unique_ptr( + new opentelemetry::exporter::otlp::OtlpGrpcExporter(opts)); + auto processor = std::unique_ptr( + new opentelemetry::sdk::trace::SimpleSpanProcessor(std::move(exporter))); + std::vector> processors; + processors.push_back(std::move(processor)); + // Default is an always-on sampler. + opentelemetry::sdk::resource::ResourceAttributes attributes; + attributes[resourcekey] = resourceval; + + auto context = std::make_shared + (std::move(processors), opentelemetry::sdk::resource::Resource::Create(attributes)); + auto provider = opentelemetry::nostd::shared_ptr( + new opentelemetry::sdk::trace::TracerProvider(context)); + // Set the global trace provider + opentelemetry::trace::Provider::SetTracerProvider(provider); + + // set global propagator + opentelemetry::context::propagation::GlobalTextMapPropagator::SetGlobalPropagator( + opentelemetry::nostd::shared_ptr( + new opentelemetry::trace::propagation::HttpTraceContext())); +} + +opentelemetry::nostd::shared_ptr get_tracer(std::string tracer_name) +{ + auto provider = opentelemetry::trace::Provider::GetTracerProvider(); + return provider->GetTracer(tracer_name); +} + +} // namespace diff --git a/src/currencyservice/tracing.js b/src/currencyservice/tracing.js deleted file mode 100644 index cc762da480..0000000000 --- a/src/currencyservice/tracing.js +++ /dev/null @@ -1,10 +0,0 @@ -const opentelemetry = require("@opentelemetry/sdk-node"); -const { getNodeAutoInstrumentations } = require("@opentelemetry/auto-instrumentations-node"); -const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc'); - -const sdk = new opentelemetry.NodeSDK({ - traceExporter: new OTLPTraceExporter(), - instrumentations: [getNodeAutoInstrumentations()] -}); - -sdk.start() \ No newline at end of file