Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 5 additions & 46 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,57 +10,16 @@ jobs:
build:
if: ${{ github.repository == 'Netflix/spectator-cpp' }}
runs-on: ubuntu-latest
env:
BUILD_DIR: "cmake-build"
BUILD_TYPE: "Debug"
CC: "gcc-11"
CXX: "g++-11"
LANG: "en_US.UTF-8"
steps:
- uses: actions/checkout@v4

- name: Conan+Cmake Cache
uses: actions/cache@v4
with:
path: |
~/.conan
~/work/spectator-cpp/spectator-cpp/cmake-build
key: ${{ runner.os }}-conan-cmake

- name: Install System Dependencies
run: |
sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
sudo apt-get update && sudo apt-get install -y binutils-dev g++-11 libiberty-dev

echo "==== cmake ===="
cmake -version

echo "==== python ===="
python3 -V

echo "==== conan ===="
pip install -r requirements.txt
conan --version
if [[ ! -f ~/.conan/profiles/default ]]; then conan profile new default --detect; fi
conan profile update settings.compiler.libcxx=libstdc++11 default

- name: Install Project Dependencies
run: |
if [[ "$BUILD_TYPE" == "Debug" ]]; then
conan install . --build --install-folder $BUILD_DIR --profile ./sanitized
else
conan install . --build=missing --install-folder $BUILD_DIR
fi

- name: Build spectator-cpp
run: |
cd $BUILD_DIR
cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
cmake --build .
echo "==== ldd ===="
ldd bin/spectatord_main || true
sudo apt-get update && sudo apt-get install -y binutils-dev g++-13 libiberty-dev

- name: Test spectator-cpp
- name: Build
run: |
cd $BUILD_DIR
GTEST_COLOR=1 ctest --verbose
./setup-venv.sh
source venv/bin/activate
./build.sh
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.DS_Store
.idea/
cmake-build-debug
CMakeUserPresets.json
cmake-build/
conan_provider.cmake
spectator/valid_chars.inc
venv/
28 changes: 21 additions & 7 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@ project(spectator-cpp)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)

add_compile_options(-fno-omit-frame-pointer "$<$<CONFIG:Debug>:-fsanitize=address>")
add_link_options(-fno-omit-frame-pointer "$<$<CONFIG:Debug>:-fsanitize=address>")
add_compile_options(-pedantic -Werror -Wall -Wno-missing-braces -fno-omit-frame-pointer "$<$<CONFIG:Debug>:-fsanitize=address>")

find_package(absl REQUIRED)
find_package(asio REQUIRED)
find_package(Backward REQUIRED)
find_package(fmt REQUIRED)
find_package(GTest REQUIRED)
find_package(spdlog REQUIRED)

include(CTest)
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()

#-- spectator_test test executable
file(GLOB spectator_test_source_files
Expand All @@ -19,7 +25,10 @@ file(GLOB spectator_test_source_files
"spectator/test_*.h"
)
add_executable(spectator_test ${spectator_test_source_files})
target_link_libraries(spectator_test spectator ${CONAN_LIBS})
target_link_libraries(spectator_test
spectator
gtest::gtest
)
add_test(
NAME spectator_test
COMMAND spectator_test
Expand All @@ -41,8 +50,13 @@ add_library(spectator
"spectator/stateless_meters.h"
"spectator/valid_chars.inc"
)
target_link_libraries(spectator ${CONAN_LIBS})
target_link_options(spectator PRIVATE "$<$<CONFIG:Release>:-static-libstdc++>")
target_link_libraries(spectator
abseil::abseil
asio::asio
Backward::Backward
fmt::fmt
spdlog::spdlog
)

#-- generator tools
add_executable(gen_valid_chars "tools/gen_valid_chars.cc")
Expand Down
20 changes: 13 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,18 +86,24 @@ fills up, the `Publisher` will not send nay meters to the sidecar. Therefore, if
meters at a high rate, you should either keep the buffer very small, or do not configure a buffer size at all,
which will fall back to the "publish immediately" mode of operation.

## Local Development
## Local & IDE Configuration

```shell
# setup python venv and activate, to gain access to conan cli
./setup-venv.sh
source venv/bin/activate

# link clion default build directory to our build directory
ln -s cmake-build cmake-build-debug

./build.sh # [clean|clean --force|skiptest]
./build.sh # [clean|clean --confirm|skiptest]
```

* CLion > Preferences > Plugins > Marketplace > Conan > Install
* CLion > Preferences > Build, Execution, Deploy > Conan > Conan Executable: $PROJECT_HOME/venv/bin/conan
* Install the Conan plugin for CLion.
* CLion > Settings > Plugins > Marketplace > Conan > Install
* Configure the Conan plugin.
* The easiest way to configure CLion to work with Conan is to build the project first from the command line.
* This will establish the `$PROJECT_HOME/CMakeUserPresets.json` file, which will allow you to choose the custom
CMake configuration created by Conan when creating a new CMake project. Using this custom profile will ensure
that sources are properly indexed and explorable.
* Open the project. The wizard will show three CMake profiles.
* Disable the default Cmake `Debug` profile.
* Enable the CMake `conan-debug` profile.
* CLion > View > Tool Windows > Conan > (gear) > Conan Executable: `$PROJECT_HOME/venv/bin/conan`
46 changes: 28 additions & 18 deletions build.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
#!/usr/bin/env bash

set -e

# usage: ./build.sh [clean|clean --confirm|skiptest]

BUILD_DIR=cmake-build
# Choose: Debug, Release, RelWithDebInfo and MinSizeRel
# Choose: Debug, Release, RelWithDebInfo and MinSizeRel. Use Debug for asan checking locally.
BUILD_TYPE=Debug

BLUE="\033[0;34m"
Expand All @@ -11,43 +15,49 @@ if [[ "$1" == "clean" ]]; then
echo -e "${BLUE}==== clean ====${NC}"
rm -rf $BUILD_DIR
rm -f spectator/*.inc
if [[ "$2" == "--force" ]]; then
# remove all packages and binaries from the local cache, to allow swapping between Debug/Release builds
conan remove '*' --force
if [[ "$2" == "--confirm" ]]; then
# remove all packages from the conan cache, to allow swapping between Release/Debug builds
conan remove "*" --confirm
fi
fi

if [[ "$OSTYPE" == "linux-gnu"* ]]; then
export CC=gcc-11
export CXX=g++-11
export CC=gcc-13
export CXX=g++-13
fi

if [[ ! -d $BUILD_DIR ]]; then
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
echo -e "${BLUE}==== configure default profile ====${NC}"
conan profile new default --detect
conan profile update settings.compiler.libcxx=libstdc++11 default
fi
if [[ ! -f "$HOME/.conan2/profiles/default" ]]; then
echo -e "${BLUE}==== create default profile ====${NC}"
conan profile detect
fi

if [[ ! -d $BUILD_DIR ]]; then
echo -e "${BLUE}==== install required dependencies ====${NC}"
if [[ "$BUILD_TYPE" == "Debug" ]]; then
conan install . --build --install-folder $BUILD_DIR --profile ./sanitized
conan install . --output-folder=$BUILD_DIR --build="*" --settings=build_type=$BUILD_TYPE --profile=./sanitized
else
conan install . --build=missing --install-folder $BUILD_DIR
conan install . --output-folder=$BUILD_DIR --build=missing
fi
fi

pushd $BUILD_DIR || exit 1
pushd $BUILD_DIR

echo -e "${BLUE}==== configure conan environment to access tools ====${NC}"
source conanbuild.sh

if [[ $OSTYPE == "darwin"* ]]; then
export MallocNanoZone=0
fi

echo -e "${BLUE}==== generate build files ====${NC}"
cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE || exit 1
cmake .. -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE

echo -e "${BLUE}==== build ====${NC}"
cmake --build . || exit 1
cmake --build .

if [[ "$1" != "skiptest" ]]; then
echo -e "${BLUE}==== test ====${NC}"
GTEST_COLOR=1 ctest --verbose
fi

popd || exit 1
popd
16 changes: 8 additions & 8 deletions conanfile.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
from conans import ConanFile
from conan import ConanFile


class SpectatorCppConan(ConanFile):
settings = "os", "compiler", "build_type", "arch"
requires = (
"abseil/20230125.3",
"asio/1.28.1",
"abseil/20240722.0",
"asio/1.32.0",
"backward-cpp/1.6",
"fmt/10.1.1",
"gtest/1.14.0",
"spdlog/1.12.0"
"fmt/11.0.2",
"gtest/1.15.0",
"spdlog/1.15.0",
)
generators = "cmake"
default_options = {}
tool_requires = ()
generators = "CMakeDeps", "CMakeToolchain"
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
conan==1.64.1
conan==2.9.2
41 changes: 32 additions & 9 deletions spectator/id.h
Original file line number Diff line number Diff line change
Expand Up @@ -186,16 +186,39 @@ struct equal_to<shared_ptr<spectator::Id>> {

} // namespace std

template <>
struct fmt::formatter<spectator::Id> {
constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
template <> struct fmt::formatter<spectator::Tags>: formatter<std::string_view> {
auto format(const spectator::Tags& tags, format_context& ctx) const -> format_context::iterator {
std::string s;
auto size = tags.size();

if (size > 0) {
// sort keys, to ensure stable output
std::vector<std::string> keys;
for (const auto& pair : tags) {
keys.push_back(pair.first);
}
std::sort(keys.begin(), keys.end());

s = "[";
for (const auto &key : keys) {
if (size > 1) {
s += key + "=" + tags.at(key) + ", ";
} else {
s += key + "=" + tags.at(key) + "]";
}
size -= 1;
}
} else {
s = "[]";
}

return fmt::formatter<std::string_view>::format(s, ctx);
}
};

// formatter for Ids
template <typename FormatContext>
auto format(const spectator::Id& id, FormatContext& context) {
return fmt::format_to(context.out(), "Id(name={}, tags={})", id.Name(),
id.GetTags());
template <> struct fmt::formatter<spectator::Id>: formatter<std::string_view> {
auto format(const spectator::Id& id, format_context& ctx) const -> format_context::iterator {
auto s = fmt::format("Id(name={}, tags={})", id.Name(), id.GetTags());
return fmt::formatter<std::string_view>::format(s, ctx);
}
};
17 changes: 13 additions & 4 deletions spectator/id_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,31 @@
#include <gtest/gtest.h>

namespace {

using spectator::Id;
using spectator::Tags;

TEST(Id, Create) {
Id id{"foo", Tags{}};
EXPECT_EQ(id.Name(), "foo");
EXPECT_EQ(id.GetTags().size(), 0);
EXPECT_EQ(fmt::format("{}", id), "Id(name=foo, tags=[])");

Id id_tags_single{"name", Tags{{"k", "v"}}};
EXPECT_EQ(id_tags_single.Name(), "name");
EXPECT_EQ(id_tags_single.GetTags().size(), 1);
EXPECT_EQ(fmt::format("{}", id_tags_single), "Id(name=name, tags=[k=v])");

Id id_tags_multiple{"name", Tags{{"k", "v"}, {"k1", "v1"}}};
EXPECT_EQ(id_tags_multiple.Name(), "name");
EXPECT_EQ(id_tags_multiple.GetTags().size(), 2);

Id id_tags{"name", Tags{{"k", "v"}, {"k1", "v1"}}};
EXPECT_EQ(id_tags.Name(), "name");
EXPECT_EQ(id_tags.GetTags().size(), 2);
EXPECT_EQ(fmt::format("{}", id_tags_multiple), "Id(name=name, tags=[k=v, k1=v1])");

std::shared_ptr<Id> id_of{Id::of("name", Tags{{"k", "v"}, {"k1", "v1"}})};
EXPECT_EQ(id_of->Name(), "name");
EXPECT_EQ(id_of->GetTags().size(), 2);
fmt::format("{}", id);
EXPECT_EQ(fmt::format("{}", *id_of), "Id(name=name, tags=[k=v, k1=v1])");
}

TEST(Id, Tags) {
Expand Down
11 changes: 5 additions & 6 deletions spectator/meter_type.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,11 @@ enum class MeterType {
};
}

// Provide a formatter for MeterType
template <>
struct fmt::formatter<spectator::MeterType> : fmt::formatter<std::string_view> {
template <typename FormatContext>
auto format(const spectator::MeterType& meter_type, FormatContext& context) {
template <> struct fmt::formatter<spectator::MeterType>: formatter<std::string_view> {
auto format(spectator::MeterType meter_type, format_context& ctx) const -> format_context::iterator {
using namespace spectator;
std::string_view s = "unknown";

switch (meter_type) {
case MeterType::AgeGauge:
s = "age-gauge";
Expand Down Expand Up @@ -56,6 +54,7 @@ struct fmt::formatter<spectator::MeterType> : fmt::formatter<std::string_view> {
s = "timer";
break;
}
return fmt::formatter<std::string_view>::format(s, context);

return fmt::formatter<std::string_view>::format(s, ctx);
}
};
11 changes: 11 additions & 0 deletions spectator/meter_type_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#include "../spectator/meter_type.h"
#include <gtest/gtest.h>

namespace {

using spectator::MeterType;

TEST(MeterType, Format) {
EXPECT_EQ(fmt::format("{}", MeterType::Counter), "counter");
}
} // namespace
1 change: 1 addition & 0 deletions spectator/stateless_meters.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once
#include "id.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/time/time.h"

Expand Down