Skip to content

Commit 36b651c

Browse files
authored
chore: add parametric system test server (#103)
1 parent 80bd66e commit 36b651c

File tree

10 files changed

+662
-0
lines changed

10 files changed

+662
-0
lines changed

test/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,5 @@ target_link_libraries(tests dd_trace_cpp-static ${COVERAGE_LIBRARIES})
4242
if(BUILD_COVERAGE)
4343
target_link_options(tests PRIVATE -fprofile-arcs -ftest-coverage)
4444
endif()
45+
46+
add_subdirectory(system-tests)

test/system-tests/CMakeLists.txt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
add_executable(parametric-http-server)
2+
3+
target_sources(parametric-http-server
4+
PRIVATE
5+
main.cpp
6+
developer_noise.cpp
7+
request_handler.cpp
8+
)
9+
10+
target_include_directories(parametric-http-server
11+
PRIVATE
12+
../../examples/http-server/common
13+
)
14+
15+
target_link_libraries(parametric-http-server dd_trace_cpp-static)
16+
17+
install(
18+
TARGETS parametric-http-server
19+
DESTINATION bin
20+
)

test/system-tests/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# System tests
2+
3+
This directory contains the HTTP server for parametric system-tests.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#include "developer_noise.h"
2+
3+
#include <chrono>
4+
#include <iomanip>
5+
#include <iostream>
6+
7+
void DeveloperNoiseLogger::developer_noise(bool enabled) {
8+
developer_noise_ = enabled;
9+
}
10+
11+
void DeveloperNoiseLogger::log_info(datadog::tracing::StringView message) {
12+
if (developer_noise_) {
13+
make_noise("INFO", [&](auto& stream) { stream << message; });
14+
}
15+
}
16+
17+
void DeveloperNoiseLogger::log_error(const LogFunc& insert_to_stream) {
18+
make_noise("ERROR", insert_to_stream);
19+
}
20+
21+
void DeveloperNoiseLogger::log_startup(const LogFunc& insert_to_stream) {
22+
make_noise("INFO", insert_to_stream);
23+
}
24+
25+
void DeveloperNoiseLogger::make_noise(std::string_view level,
26+
const LogFunc& insert_to_stream) {
27+
std::time_t t = std::time(0); // get time now
28+
auto now = std::localtime(&t);
29+
30+
std::lock_guard<std::mutex> lock(mutex_);
31+
32+
std::cerr << "[" << std::put_time(now, "%c") << "] [" << level << "] ";
33+
insert_to_stream(std::cerr);
34+
std::cerr << std::endl;
35+
}
36+
37+
std::shared_ptr<DeveloperNoiseLogger> make_logger() {
38+
// Initialize a logger that handles errors from the library as well as
39+
// comforting developer-noise from the tracing service.
40+
auto logger = std::make_shared<DeveloperNoiseLogger>();
41+
42+
// Enable developer noise when a specific environment variable is set.
43+
auto verbose_env = std::getenv("CPP_PARAMETRIC_TEST_VERBOSE");
44+
if (verbose_env) {
45+
try {
46+
if (std::stoi(verbose_env) == 1) {
47+
logger->developer_noise(true);
48+
}
49+
} catch (...) {
50+
}
51+
}
52+
53+
return logger;
54+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#pragma once
2+
3+
#include <datadog/logger.h>
4+
5+
#include <memory>
6+
#include <mutex>
7+
#include <sstream>
8+
9+
class DeveloperNoiseLogger : public datadog::tracing::Logger {
10+
bool developer_noise_ = false;
11+
std::mutex mutex_;
12+
13+
public:
14+
void developer_noise(bool enabled);
15+
void log_info(datadog::tracing::StringView message);
16+
void log_error(const LogFunc&) override;
17+
void log_startup(const LogFunc&) override;
18+
using Logger::log_error; // other overloads of log_error
19+
20+
private:
21+
void make_noise(std::string_view level, const LogFunc&);
22+
};
23+
24+
std::shared_ptr<DeveloperNoiseLogger> make_logger();

test/system-tests/main.cpp

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
#include <datadog/optional.h>
2+
#include <datadog/span_config.h>
3+
#include <datadog/tracer.h>
4+
#include <datadog/tracer_config.h>
5+
6+
#include <chrono>
7+
#include <datadog/json.hpp>
8+
#include <iostream>
9+
#include <optional>
10+
#include <string_view>
11+
#include <unordered_map>
12+
13+
#include "developer_noise.h"
14+
#include "httplib.h"
15+
#include "request_handler.h"
16+
17+
// `hard_stop` is installed as a signal handler for `SIGTERM`.
18+
// For some reason, the default handler was not being called.
19+
void hard_stop(int /*signal*/) { std::exit(0); }
20+
21+
std::optional<uint16_t> get_port() {
22+
try {
23+
auto port_env = std::getenv("APM_TEST_CLIENT_SERVER_PORT");
24+
if (port_env == nullptr) {
25+
return std::nullopt;
26+
}
27+
28+
uint16_t port = std::atoi(port_env);
29+
return port;
30+
} catch (...) {
31+
return std::nullopt;
32+
}
33+
}
34+
35+
void print_usage(std::string_view app) {
36+
// clang-format off
37+
std::cout << app << "\n\n"
38+
<< "Usage: HTTP server for parametric system tests\n\n"
39+
<< "-h, --help\t\tPrint this help message.\n"
40+
<< "-v, --version\t\tPrint the version of dd-trace-cpp.\n\n"
41+
<< "Environment variables:\n\n"
42+
<< "APM_TEST_CLIENT_SERVER_PORT\tDefines port to use."
43+
<< "\n";
44+
// clang-format on
45+
}
46+
47+
int main(int argc, char* argv[]) {
48+
if (argc > 1) {
49+
for (int i = 0; i < argc; ++i) {
50+
const std::string_view arg{argv[i]};
51+
if (arg == "-h" || arg == "--help") {
52+
print_usage(argv[0]);
53+
return 0;
54+
} else if (arg == "-v" || arg == "--version") {
55+
std::cout << datadog::tracing::tracer_version << "\n";
56+
return 0;
57+
}
58+
}
59+
}
60+
61+
auto logger = make_logger();
62+
63+
auto port = get_port();
64+
if (!port) {
65+
logger->log_error(
66+
"environment variable APM_TEST_CLIENT_SERVER_PORT is not set or the "
67+
"port is not valid");
68+
return 1;
69+
}
70+
71+
// An event scheduler needs to be shared between the TracingService and the
72+
// tracer.
73+
auto event_scheduler = std::make_shared<ManualScheduler>();
74+
75+
datadog::tracing::TracerConfig config;
76+
config.logger = logger;
77+
config.agent.event_scheduler = event_scheduler;
78+
config.service = "cpp-parametric-test";
79+
config.environment = "staging";
80+
config.name = "http.request";
81+
82+
auto finalized_config = datadog::tracing::finalize_config(config);
83+
if (auto error = finalized_config.if_error()) {
84+
logger->log_error(error->with_prefix("unable to initialize tracer:"));
85+
return 1;
86+
}
87+
88+
RequestHandler handler(*finalized_config, event_scheduler, logger);
89+
90+
httplib::Server svr;
91+
svr.Post("/trace/span/start",
92+
[&handler](const httplib::Request& req, httplib::Response& res) {
93+
handler.on_span_start(req, res);
94+
});
95+
svr.Post("/trace/span/finish",
96+
[&handler](const httplib::Request& req, httplib::Response& res) {
97+
handler.on_span_end(req, res);
98+
});
99+
svr.Post("/trace/span/set_meta",
100+
[&handler](const httplib::Request& req, httplib::Response& res) {
101+
handler.on_set_meta(req, res);
102+
});
103+
svr.Post("/trace/span/inject_headers",
104+
[&handler](const httplib::Request& req, httplib::Response& res) {
105+
handler.on_inject_headers(req, res);
106+
});
107+
svr.Post("/trace/span/flush",
108+
[&handler](const httplib::Request& req, httplib::Response& res) {
109+
handler.on_span_flush(req, res);
110+
});
111+
svr.Post("/trace/stats/flush",
112+
[&handler](const httplib::Request& req, httplib::Response& res) {
113+
handler.on_stats_flush(req, res);
114+
});
115+
116+
// Not implemented
117+
svr.Post("/trace/span/set_metric",
118+
[&handler](const httplib::Request& req, httplib::Response& res) {
119+
handler.on_set_metric(req, res);
120+
});
121+
122+
svr.set_logger([&logger](const auto& req, const auto&) {
123+
std::string msg{req.method};
124+
msg += " ";
125+
msg += req.path;
126+
msg += " ";
127+
msg += req.version;
128+
logger->log_info(msg);
129+
130+
if (!req.body.empty()) {
131+
msg = " body: ";
132+
msg += req.body;
133+
logger->log_info(msg);
134+
}
135+
});
136+
137+
svr.set_exception_handler([](const auto&, auto& res, std::exception_ptr ep) {
138+
try {
139+
std::rethrow_exception(ep);
140+
} catch (const nlohmann::json::exception& e) {
141+
// clang-format off
142+
nlohmann::json j{
143+
{"detail", {
144+
{"loc", nlohmann::json::array({__FILE__, __LINE__})},
145+
{"type", "JSON Parsing error"},
146+
{"msg", e.what()}
147+
}}
148+
};
149+
// clang-format on
150+
151+
res.set_content(j.dump(), "application/json");
152+
res.status = 422;
153+
return;
154+
} catch (std::exception& e) {
155+
res.set_content(e.what(), "text/plain");
156+
} catch (...) {
157+
res.set_content("Unknown Exception", "text/plain");
158+
}
159+
160+
res.status = 500;
161+
});
162+
163+
std::signal(SIGTERM, hard_stop);
164+
svr.listen("0.0.0.0", *port);
165+
return 0;
166+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#pragma once
2+
3+
#include <datadog/event_scheduler.h>
4+
5+
#include <cassert>
6+
#include <datadog/json.hpp>
7+
8+
struct ManualScheduler : public datadog::tracing::EventScheduler {
9+
std::function<void()> flush_traces = nullptr;
10+
std::function<void()> flush_telemetry = nullptr;
11+
12+
Cancel schedule_recurring_event(
13+
std::chrono::steady_clock::duration /* interval */,
14+
std::function<void()> callback) override {
15+
assert(callback != nullptr);
16+
17+
// NOTE: This depends on the precise order that dd-trace-cpp sets up the
18+
// `schedule_recurring_event`s for traces and telemetry.
19+
if (flush_traces == nullptr) {
20+
flush_traces = callback;
21+
return {};
22+
}
23+
if (flush_telemetry == nullptr) {
24+
flush_telemetry = callback;
25+
return {};
26+
}
27+
return []() {};
28+
}
29+
30+
nlohmann::json config_json() const override {
31+
return nlohmann::json::object({{"type", "ManualScheduler"}});
32+
}
33+
};

0 commit comments

Comments
 (0)