Skip to content

out_cloudwatch_logs: reuse entity record accessors across records - #12256

Open
ParakhJaggi wants to merge 1 commit into
fluent:masterfrom
ParakhJaggi:perf/out_cloudwatch_logs-cache-entity-ra
Open

out_cloudwatch_logs: reuse entity record accessors across records#12256
ParakhJaggi wants to merge 1 commit into
fluent:masterfrom
ParakhJaggi:perf/out_cloudwatch_logs-cache-entity-ra

Conversation

@ParakhJaggi

@ParakhJaggi ParakhJaggi commented Aug 10, 2026

Copy link
Copy Markdown

parse_entity() built and destroyed ten record accessors for every single record whenever add_entity is enabled:

for (i = 0; field_map[i].path; i++) {
    ra = flb_ra_create((char *) field_map[i].path, FLB_FALSE);
    ...
    flb_ra_destroy(ra);
}

The ten paths are compile time constants, so this re-parses the same accessor expressions on every record and produces a churn of allocations and frees proportional to ingested log volume.

This change compiles the accessors once during plugin init and stores them on the context, following the pattern already used for ra_group and ra_stream, then releases them in flb_cloudwatch_ctx_destroy().

Notes:

  • The accessors are only built when add_entity is enabled, so deployments that do not use entity support allocate nothing extra.
  • The path table and the per record field map are both indexed by the same ENTITY_RA_* slots using designated initializers, so the two tables cannot silently drift out of order.
  • Behaviour is unchanged. Previously a failed flb_ra_create() was skipped silently per record; now a failure is reported once and fails plugin init, which is consistent with how the existing log_group_template and log_stream_template accessors are handled.

We hit this on an EKS cluster running the CloudWatch Container Insights daemonset with add_entity true, where fluent-bit is on the per record path for every container log line on the node.


Enter [N/A] in the box, if an item is not applicable to your change.

Testing
Before we can approve your change; please submit the following in a comment:

  • Example configuration file for the change
  • Debug log output from testing the change
  • Attached Valgrind output that shows no leaks or memory corruption was found

If this is a change to packaging of containers or native binaries then please confirm it works for all targets.

  • [N/A] Run local packaging test showing all targets (including any new ones) build.
  • [N/A] Set ok-package-test label to test for all targets (requires maintainer to do).

Documentation

  • [N/A] Documentation required for this feature

Backporting

  • Backport to latest stable release.

Fluent Bit is licensed under Apache 2.0, by submitting this pull request I understand that this code will be released under the terms of that license.

Summary by CodeRabbit

  • Bug Fixes
    • Improved CloudWatch Logs entity field processing by reusing prepared accessors.
    • Added safer initialization and cleanup when entity configuration is enabled.
    • CloudWatch Logs now handles accessor setup failures more reliably.

parse_entity() built and destroyed ten record accessors for every single
record when add_entity is enabled. The paths are compile time constants,
so this repeated the same parse work per record and produced a steady
churn of allocations and frees proportional to the ingested log volume.

Compile the accessors once during plugin init and store them on the
context, following the same pattern already used for ra_group and
ra_stream, then release them in flb_cloudwatch_ctx_destroy().

The accessors are only built when add_entity is enabled, so deployments
that do not use entity support are unaffected.

The paths and the field map are indexed by the same ENTITY_RA_* slots so
the two tables cannot drift out of order.

Signed-off-by: ParakhJaggi <parakhjaggi@gmail.com>
@ParakhJaggi
ParakhJaggi requested a review from a team as a code owner August 10, 2026 16:04
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

CloudWatch entity record accessors are stored in an indexed context array. The output initializes them once when add_entity is enabled, reuses them during entity parsing, and releases them during context destruction.

Changes

CloudWatch entity accessor lifecycle

Layer / File(s) Summary
Accessor storage and API contract
plugins/out_cloudwatch_logs/cloudwatch_logs.h, plugins/out_cloudwatch_logs/cloudwatch_api.h
The context stores indexed entity accessors. Public initialization and destruction declarations are added.
Accessor initialization and cleanup
plugins/out_cloudwatch_logs/cloudwatch_api.c, plugins/out_cloudwatch_logs/cloudwatch_logs.c
Initialization creates all accessors and cleans up partial state on failure. CloudWatch setup and destruction invoke the lifecycle functions.
Cached accessor parsing
plugins/out_cloudwatch_logs/cloudwatch_api.c
parse_entity reuses cached accessors and maps resolved values to entity fields and filtering metadata.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested reviewers: cosmo0920

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: reusing CloudWatch entity record accessors across records.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@plugins/out_cloudwatch_logs/cloudwatch_api.c`:
- Around line 1268-1329: Add unit tests in the existing CloudWatch test suite
covering entity_ra_init() success, partial flb_ra_create() failure, and cleanup
through entity_ra_destroy() that releases only initialized accessors. Exercise
parse_entity() with missing fields and non-string values, verifying fields and
counters remain correct without crashes or invalid updates. Reuse the existing
CloudWatch test helpers and accessor-path symbols rather than adding unrelated
production changes.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 65897885-8745-4be0-a1d0-362aa7935cc6

📥 Commits

Reviewing files that changed from the base of the PR and between a1d6fb1 and 95823e4.

📒 Files selected for processing (4)
  • plugins/out_cloudwatch_logs/cloudwatch_api.c
  • plugins/out_cloudwatch_logs/cloudwatch_api.h
  • plugins/out_cloudwatch_logs/cloudwatch_logs.c
  • plugins/out_cloudwatch_logs/cloudwatch_logs.h

Comment on lines +1268 to 1329
int entity_ra_init(struct flb_cloudwatch *ctx)
{
int i;

for (i = 0; i < ENTITY_RA_MAX; i++) {
ctx->entity_ra[i] = flb_ra_create((char *) entity_ra_paths[i],
FLB_FALSE);
if (ctx->entity_ra[i] == NULL) {
flb_plg_error(ctx->ins, "Could not parse entity record accessor %s",
entity_ra_paths[i]);
entity_ra_destroy(ctx);
return -1;
}
}

return 0;
}

void parse_entity(struct flb_cloudwatch *ctx, entity *entity,
msgpack_object map, int map_size)
{
struct flb_record_accessor *ra;
struct flb_ra_value *val;
int i;

struct {
const char *path;
char **field;
int *filter_count;
int *found_flag;
} field_map[] = {
{"$kubernetes['aws_entity_service_name']", &entity->key_attributes->name,
} field_map[ENTITY_RA_MAX] = {
[ENTITY_RA_SERVICE_NAME] = {&entity->key_attributes->name,
&entity->filter_count, &entity->service_name_found},
{"$kubernetes['aws_entity_environment']", &entity->key_attributes->environment,
[ENTITY_RA_ENVIRONMENT] = {&entity->key_attributes->environment,
&entity->filter_count, &entity->environment_found},
{"$kubernetes['namespace_name']", &entity->attributes->namespace,
[ENTITY_RA_NAMESPACE] = {&entity->attributes->namespace,
NULL, NULL},
{"$kubernetes['host']", &entity->attributes->node, NULL, NULL},
{"$kubernetes['aws_entity_cluster']", &entity->attributes->cluster_name,
[ENTITY_RA_NODE] = {&entity->attributes->node, NULL, NULL},
[ENTITY_RA_CLUSTER] = {&entity->attributes->cluster_name,
&entity->filter_count, NULL},
{"$kubernetes['aws_entity_workload']", &entity->attributes->workload,
[ENTITY_RA_WORKLOAD] = {&entity->attributes->workload,
&entity->filter_count, NULL},
{"$kubernetes['aws_entity_name_source']", &entity->attributes->name_source,
[ENTITY_RA_NAME_SOURCE] = {&entity->attributes->name_source,
&entity->filter_count, &entity->name_source_found},
{"$kubernetes['aws_entity_platform']", &entity->attributes->platform_type,
[ENTITY_RA_PLATFORM] = {&entity->attributes->platform_type,
&entity->filter_count, NULL},
{"$aws_entity_ec2_instance_id", &entity->attributes->instance_id,
[ENTITY_RA_INSTANCE_ID] = {&entity->attributes->instance_id,
&entity->root_filter_count, NULL},
{"$aws_entity_account_id", &entity->key_attributes->account_id,
&entity->root_filter_count, NULL},
{NULL, NULL, NULL, NULL}
[ENTITY_RA_ACCOUNT_ID] = {&entity->key_attributes->account_id,
&entity->root_filter_count, NULL}
};

for (i = 0; field_map[i].path; i++) {
ra = flb_ra_create((char *) field_map[i].path, FLB_FALSE);
if (!ra) {

for (i = 0; i < ENTITY_RA_MAX; i++) {
if (ctx->entity_ra[i] == NULL) {
continue;
}
val = flb_ra_get_value_object(ra, map);

val = flb_ra_get_value_object(ctx->entity_ra[i], map);
if (val) {
set_entity_field(field_map[i].field, val, field_map[i].filter_count,
field_map[i].found_flag);
flb_ra_key_value_destroy(val);
}

flb_ra_destroy(ra);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 4 --glob '*.{c,h}' \
  '\b(entity_ra_init|entity_ra_destroy|parse_entity|flb_ra_create)\s*\(' \
  plugins/out_cloudwatch_logs tests

rg -n -C 4 --glob '*.{c,h}' \
  'add_entity|aws_entity_service_name|aws_entity_environment|FLB_RA_STRING' \
  plugins/out_cloudwatch_logs tests

Repository: fluent/fluent-bit

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Files matching cloudwatch tests:\n'
fd -i 'cloudwatch' tests | sed -n '1,80p'

printf '\nReferences to entity RA/entity fields/services in out_cloudwatch_logs:\n'
rg -n --glob '*.{c,h}' \
  'entity_ra_init|entity_ra_destroy|parse_entity|set_entity_field|add_entity|aws_entity_service_name|aws_entity_environment|aws_entity_cluster|aws_entity_workload|aws_entity_ec2_instance_id|aws_entity_account_id|ENTITY_RA_|filter_count|found_flag' \
  plugins/out_cloudwatch_logs tests | sed -n '1,220p'

printf '\nRelevant init/cleanup code:\n'
sed -n '140,172p;508,526p' plugins/out_cloudwatch_logs/cloudwatch_logs.c
sed -n '1200,1235p;1238,1345p' plugins/out_cloudwatch_logs/cloudwatch_api.c

Repository: fluent/fluent-bit

Length of output: 15857


Add unit coverage for entity accessor lifecycle.

tests/runtime/out_cloudwatch.c does not cover the new entity_ra_init()/entity_ra_destroy() path or parse_entity() field handling. Add tests for successful initialization, partial accessor initialization failure, missing fields, and non-string entity fields, including cleanup that releases only initialized accessors.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/out_cloudwatch_logs/cloudwatch_api.c` around lines 1268 - 1329, Add
unit tests in the existing CloudWatch test suite covering entity_ra_init()
success, partial flb_ra_create() failure, and cleanup through
entity_ra_destroy() that releases only initialized accessors. Exercise
parse_entity() with missing fields and non-string values, verifying fields and
counters remain correct without crashes or invalid updates. Reuse the existing
CloudWatch test helpers and accessor-path symbols rather than adding unrelated
production changes.

Source: Coding guidelines

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant