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
13 changes: 13 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Dobby Copilot Instructions

Use the custom instructions in `.github/instructions/*.instructions.md` as the primary coding guidance for this repository.

## Review Comment Linking

When leaving review comments based on a rule from `.github/instructions/General.instructions.md`, include a direct link to the section in the same format:

Refer: https://github.com/rdkcentral/Dobby/blob/develop/.github/instructions/General.instructions.md#critical-logging

## Scope

These rules are intended to keep generated code and review comments aligned with Dobby's existing codebase conventions, plugin lifecycle model, build setup, and openspec documents.
98 changes: 98 additions & 0 deletions .github/instructions/General.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
---
applyTo: "**/*.{cpp,h,cc,cxx,hpp},**/CMakeLists.txt,**/*.cmake,**/*.sh"
---

# Instruction Summary
1. Critical Logging
2. Recoverable Error Reporting
3. Null Safety and Pointer Style
4. Plugin Lifecycle Discipline
5. RDK Plugin Hook Contract
6. CMake and Plugin Onboarding Compliance
7. Spec and Schema Synchronization

### Critical Logging

### Requirement

Use Dobby logging macros from `AppInfrastructure/Logging/include/Logging.h` for failures:

```cpp
AI_LOG_ERROR("failed to parse config for plugin '%s'", pluginName.c_str());
```

If the function returns failure, log once with enough context and return the appropriate failure value.

### Incorrect Example

```cpp
printf("failed to parse config\n");
return false;
```

### Recoverable Error Reporting

### Requirement

Use `AI_LOG_WARN` for recoverable or fallback behavior.

```cpp
if (missingOptionalField)
{
AI_LOG_WARN("optional field missing, using default");
}
```

Do not log expected fallback behavior as hard errors.

### Null Safety and Pointer Style

### Requirement

- Use `nullptr` instead of `NULL` in all new code.
- Preserve nearby comparison style (`nullptr == ptr` or `ptr == nullptr`) to avoid style churn inside existing files.
- Validate pointers before dereference and fail with contextual log messages.

### Plugin Lifecycle Discipline

### Requirement

For Dobby RDK plugins and daemon lifecycle-managed components:

- Keep constructors lightweight.
- Do heavy setup in lifecycle hook methods.
- Release resources in reverse order of allocation.
- Reset internal state after cleanup.

Where hook/teardown fails, log with enough context to diagnose container and plugin state.

### RDK Plugin Hook Contract

### Requirement

- Implement `IDobbyRdkPlugin` contract correctly.
- Keep `name()` stable.
- Set `hookHints()` to match only implemented hooks.
- Prefer inheriting from `RdkPluginBase` and override only needed hooks.
- Declare inter-plugin ordering requirements through `getDependencies()` when required.

### CMake and Plugin Onboarding Compliance

### Requirement

When introducing a new plugin in top-level `CMakeLists.txt`:

1. Add matching `PLUGIN_<NAME>` option and `add_subdirectory(...)`.
2. Update `.github/workflows/L1-tests.yml` plugin build flags.
3. Update `.github/workflows/L2-tests.yml` where integration coverage is needed.
4. Update `cov_build.sh` to include the plugin build flag.

This keeps CI and static analysis coverage aligned with source registration.

### Spec and Schema Synchronization

### Requirement

- Keep openspec docs in `openspec/specs/` in sync with behavior changes.
- For runtime schema changes under `bundle/runtime-schemas/`, re-run CMake so generated headers are refreshed.
- Do not merge behavior changes that leave specs stale.
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ cmake_minimum_required( VERSION 3.7.0 )
include(GNUInstallDirs)

# Project setup
project( Dobby VERSION "3.21.0" )
project( Dobby VERSION "3.22.0" )


# Set the major and minor version numbers of dobby (also used by plugins)
set( DOBBY_MAJOR_VERSION 3 )
set( DOBBY_MINOR_VERSION 21 )
set( DOBBY_MINOR_VERSION 22 )
set( DOBBY_MICRO_VERSION 0 )

set(INSTALL_CMAKE_DIR lib/cmake/Dobby)
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ The table below lists the supported top-level fields. Fields marked **mandatory*
| `version` | string | Yes | Spec version. Currently `"1.0"` or `"1.1"`. |
| `args` | array | Yes | Command and arguments to run inside the container. |
| `user` | object | Yes | `uid` and `gid` the container process runs as. |
| `memLimit` | integer | Yes | Memory limit in bytes (`memory.limit_in_bytes`). Values below 256 KiB are accepted but will only generate a warning and may not be effective. |
| `swapLimit` | integer | No | Swap+memory limit in bytes (`memory.memsw.limit_in_bytes`). Must be ≥ `memLimit`. Defaults to unlimited (-1) when absent. |
| `memLimit` | integer | Yes | Requested memory limit in bytes. When `swapLimit` is present, this value is scaled down according to the host's RAM and active zram swap capacity before being applied to `memory.limit_in_bytes`. Other swap types, such as swapfiles and partitions, are ignored. Values below 256 KiB are accepted but will only generate a warning and may not be effective. |
| `swapLimit` | integer | No | Swap+memory limit in bytes (`memory.memsw.limit_in_bytes`). Must be ≥ the effective scaled memory limit. Defaults to unlimited (-1) when absent. |
| `env` | array | No | Environment variables in `"KEY=VALUE"` format. |
| `cwd` | string | No | Working directory inside the container. |
| `console` | object | No | Console log settings: `path` and `limit` (bytes). |
Expand Down Expand Up @@ -168,7 +168,7 @@ The table below lists the supported top-level fields. Fields marked **mandatory*
}
```

`swapLimit` sets the combined memory+swap ceiling enforced by the kernel cgroup (`memory.memsw.limit_in_bytes`). When omitted, memory+swap is unlimited (-1), allowing the container to use as much swap as the system provides.
When `swapLimit` is present, `memLimit` is scaled down based on the host's active zram-swap-to-RAM ratio before it is applied to `memory.limit_in_bytes`. Swapfiles and swap partitions are not included in this ratio. `swapLimit` sets the combined memory+swap ceiling enforced by the kernel cgroup (`memory.memsw.limit_in_bytes`). When omitted, `memLimit` is applied directly and memory+swap is unlimited (-1), allowing the container to use as much swap as the system provides.

## DobbyTool
This is a simple command line tool that is used for debugging purporses. It connects to the Dobby daemon over dbus and allows for debugging and testing containers.
Expand Down
5 changes: 5 additions & 0 deletions bundle/lib/include/DobbySpecConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

#include <set>
#include <bitset>
#include <cstdint>
#include <memory>

namespace ctemplate {
Expand Down Expand Up @@ -174,6 +175,9 @@ class DobbySpecConfig : public DobbyConfig
static void addGpuDevNodes(const std::shared_ptr<const IDobbySettings::HardwareAccessSettings> &settings,
ctemplate::TemplateDictionary *dict);

static int64_t calculatePhysicalMemoryLimit(int64_t memLimit,
double swapToRamRatio);

static void addVpuDevNodes(const std::shared_ptr<const IDobbySettings::HardwareAccessSettings> &settings,
ctemplate::TemplateDictionary *dict);

Expand All @@ -187,6 +191,7 @@ class DobbySpecConfig : public DobbyConfig
private:
bool mValid;
ctemplate::TemplateDictionary* mDictionary;
double mZramSwapToRamRatio;

private:
Json::Value mSpec;
Expand Down
100 changes: 94 additions & 6 deletions bundle/lib/source/DobbySpecConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include <sys/capability.h>
#include <sys/stat.h>
#include <fstream>
#include <sstream>

// Compile time generated strings that (in theory) speeds up the processing
// of ctemplate expanding
Expand Down Expand Up @@ -196,6 +197,75 @@ static const ctemplate::StaticTemplateString SECCOMP_SYSCALLS =

int DobbySpecConfig::mNumCores = -1;

int64_t DobbySpecConfig::calculatePhysicalMemoryLimit(int64_t memLimit,
double swapToRamRatio)
{
return static_cast<int64_t>((1.0 / (1.0 + swapToRamRatio)) * static_cast<double>(memLimit));
}

static double getZramSwapToRamRatio()
{
std::ifstream meminfo("/proc/meminfo");
if (!meminfo.is_open())
{
return 0.0;
Comment thread
B-Larsen marked this conversation as resolved.
}

unsigned long long memTotalKb = 0;
std::string line;

while (std::getline(meminfo, line))
{
std::istringstream iss(line);
std::string key;
iss >> key;

if (key == "MemTotal:")
{
unsigned long long value = 0;
std::string units;
if (!(iss >> value >> units))
{
return 0.0;
}
memTotalKb = value;
}
}

if (memTotalKb == 0)
{
return 0.0;
}

std::ifstream swaps("/proc/swaps");
if (!swaps.is_open())
{
return 0.0;
}

unsigned long long zramTotalKb = 0;
std::string filename;
std::string type;
unsigned long long sizeKb = 0;
unsigned long long usedKb = 0;
int priority = 0;
std::getline(swaps, line);
while (swaps >> filename >> type >> sizeKb >> usedKb >> priority)
{
if (filename.rfind("/dev/zram", 0) == 0)
{
zramTotalKb += sizeKb;
}
}

if (zramTotalKb == 0)
{
return 0.0;
}

return static_cast<double>(zramTotalKb) / static_cast<double>(memTotalKb);
}

// TODO: should we only allowed these if a network namespace is enabled ?
const std::map<std::string, int> DobbySpecConfig::mAllowedCaps =
{
Expand Down Expand Up @@ -225,6 +295,7 @@ DobbySpecConfig::DobbySpecConfig(const std::shared_ptr<IDobbyUtils> &utils,
, mDefaultPlugins(settings->defaultPlugins())
, mRdkPluginsData(settings->rdkPluginsData())
, mDictionary(nullptr)
, mZramSwapToRamRatio(0.0)
, mConf(nullptr)
, mSpecVersion(SpecVersion::Unknown)
, mUserId(-1)
Expand Down Expand Up @@ -308,6 +379,7 @@ DobbySpecConfig::DobbySpecConfig(const std::shared_ptr<IDobbyUtils> &utils,
, mGpuSettings(settings->gpuAccessSettings())
, mVpuSettings(settings->vpuAccessSettings())
, mDictionary(nullptr)
, mZramSwapToRamRatio(0.0)
, mConf(nullptr)
, mSpecVersion(SpecVersion::Unknown)
, mUserId(-1)
Expand Down Expand Up @@ -532,6 +604,11 @@ bool DobbySpecConfig::parseSpec(ctemplate::TemplateDictionary* dictionary,
}
}

if (mSpec.isMember("swapLimit") && mSpec["swapLimit"].isIntegral())
{
mZramSwapToRamRatio = getZramSwapToRamRatio();
}

// step 2 - get the version number of the spec first, it may determine how
// subsequent fields are processed
const Json::Value version = mSpec["version"];
Expand Down Expand Up @@ -1306,7 +1383,14 @@ bool DobbySpecConfig::processMemLimit(const Json::Value& value,
AI_LOG_WARN("memory limit looks dangerously low");
}

dictionary->SetIntValue(MEM_LIMIT, memLimit);
// Only apply the zram-aware adjustment when swapLimit is explicitly
// set; otherwise keep memLimit as-is to match the memory.limit_in_bytes.
unsigned physLimit = memLimit;
if (mSpec.isMember("swapLimit") && mSpec["swapLimit"].isIntegral())
{
physLimit = static_cast<unsigned>(calculatePhysicalMemoryLimit(memLimit, mZramSwapToRamRatio));
Comment thread
B-Larsen marked this conversation as resolved.
}
dictionary->SetIntValue(MEM_LIMIT, physLimit);

return true;
}
Expand All @@ -1319,8 +1403,9 @@ bool DobbySpecConfig::processMemLimit(const Json::Value& value,
* allowing swap to be configured independently of the memory limit. When
* absent the swap limit is set to -1 (unlimited).
*
* The kernel requires swap >= memLimit, so an error is returned if the
* supplied value is smaller than the memLimit already set.
* The kernel requires swap >= the effective physical memory limit, so an
* error is returned if the supplied value is smaller than the adjusted
* memory.limit_in_bytes value.
*
* Example json:
*
Expand Down Expand Up @@ -1363,10 +1448,13 @@ bool DobbySpecConfig::processSwapLimit(const Json::Value& value,
AI_LOG_ERROR("memLimit is negative; cannot validate swapLimit");
return false;
}
if (memSwapSigned < memLimitSigned)

// swapLimit must be at least the effective zram-adjusted physical limit.
const int64_t physLimit = calculatePhysicalMemoryLimit(memLimitSigned, mZramSwapToRamRatio);
if (memSwapSigned < physLimit)
{
AI_LOG_ERROR("swapLimit (%" PRId64 ") must be >= memLimit (%" PRId64 ")",
memSwapSigned, memLimitSigned);
AI_LOG_ERROR("swapLimit (%" PRId64 ") must be >= memory.limit_in_bytes (%" PRId64 ")",
memSwapSigned, physLimit);
return false;
}
}
Expand Down
23 changes: 17 additions & 6 deletions tests/L1_testing/tests/DobbySpecConfigTest/DobbySpecConfigTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ static const char* kSpecSwapBelowLimit = R"({
"args": ["/bin/true"],
"user": { "uid": 1000, "gid": 1000 },
"memLimit": 5996544,
"swapLimit": 2998272
"swapLimit": 0
})";

// ── Spec with capabilities ────────────────────────────────────────────────────
Expand Down Expand Up @@ -208,15 +208,24 @@ class DobbySpecConfigTest : public ::testing::Test
// ── Tests ─────────────────────────────────────────────────────────────────────

/**
* When 'swapLimit' is absent, MEM_SWAP must default to -1 (unlimited).
* When 'swapLimit' is absent, MEM_SWAP must default to -1 (unlimited) and
* MEM_LIMIT must be left as the raw memLimit (no zram adjustment applied).
*/
TEST_F(DobbySpecConfigTest, SwapLimit_DefaultsToUnlimited)
{
auto cfg = makeConfig(kSpecMemOnly);
EXPECT_TRUE(cfg->isValid());

EXPECT_EQ(expandMemTemplate(*cfg), "LIMIT=2998272 SWAP=-1");
}

TEST_F(DobbySpecConfigTest, PhysicalMemoryLimit_UsesRamShareOfTotalCapacity)
{
EXPECT_EQ(DobbySpecConfig::calculatePhysicalMemoryLimit(1000, 0.0), 1000);
EXPECT_EQ(DobbySpecConfig::calculatePhysicalMemoryLimit(1000, 1.0), 500);
EXPECT_EQ(DobbySpecConfig::calculatePhysicalMemoryLimit(1000, 3.0), 250);
}

/**
* When 'swapLimit' is greater than 'memLimit', MEM_SWAP must be set to the
* supplied swap limit independently of MEM_LIMIT.
Expand All @@ -225,7 +234,8 @@ TEST_F(DobbySpecConfigTest, SwapLimit_SetIndependently)
{
auto cfg = makeConfig(kSpecWithSwap);
EXPECT_TRUE(cfg->isValid());
EXPECT_EQ(expandMemTemplate(*cfg), "LIMIT=2998272 SWAP=5996544");

EXPECT_NE(expandMemTemplate(*cfg).find("SWAP=5996544"), std::string::npos);
Comment thread
B-Larsen marked this conversation as resolved.
}

/**
Expand All @@ -236,12 +246,13 @@ TEST_F(DobbySpecConfigTest, SwapLimit_EqualToMemLimit_Succeeds)
{
auto cfg = makeConfig(kSpecSwapEqualsLimit);
EXPECT_TRUE(cfg->isValid());
EXPECT_EQ(expandMemTemplate(*cfg), "LIMIT=2998272 SWAP=2998272");

EXPECT_NE(expandMemTemplate(*cfg).find("SWAP=2998272"), std::string::npos);
}

/**
* When 'swapLimit' < 'memLimit', processSwapLimit must reject the value
* and parsing must fail (kernel requires memsw >= mem).
* When 'swapLimit' is below the effective physical memory limit,
* processSwapLimit must reject the value and parsing must fail.
*/
TEST_F(DobbySpecConfigTest, SwapLimit_LessThanMemLimit_Fails)
{
Expand Down
Loading
Loading