Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Container CPU Utilization Resource Monitor #37792

Open
wants to merge 33 commits into
base: main
Choose a base branch
from

Conversation

nix1n
Copy link

@nix1n nix1n commented Dec 23, 2024

Risk Level: Low
Testing: Unit tests
Docs Changes:
Release Notes:
Platform Specific Features: Today this is only implemented for Linux

In my org we use envoy in containerised K8s environment, this PR will allow us to trigger overload actions based on the container cpu utilization of the pod where envoy is running.

Copy link

Hi @nix1n, welcome and thank you for your contribution.

We will try to review your Pull Request as quickly as possible.

In the meantime, please take a look at the contribution guidelines if you have not done so already.

🐱

Caused by: #37792 was opened by nix1n.

see: more, trace.

Copy link

CC @envoyproxy/api-shepherds: Your approval is needed for changes made to (api/envoy/|docs/root/api-docs/).
envoyproxy/api-shepherds assignee is @markdroth
CC @envoyproxy/api-watchers: FYI only for changes made to (api/envoy/|docs/root/api-docs/).

🐱

Caused by: #37792 was opened by nix1n.

see: more, trace.

…edding in K8s environment

Signed-off-by: nix1n <nikhil.murari@hotstar.com>
Signed-off-by: nix1n <nikhil.murari@hotstar.com>
Signed-off-by: nix1n <nikhil.murari@hotstar.com>
Signed-off-by: nix1n <nikhil.murari@hotstar.com>
Signed-off-by: nix1n <nikhil.murari@hotstar.com>
@KBaichoo KBaichoo self-assigned this Dec 23, 2024
Copy link
Contributor

@KBaichoo KBaichoo left a comment

Choose a reason for hiding this comment

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

Thank you for working on this! I think we can try to reuse some of the existing components from the other cpu monitor to make this even better.

previous_envoy_container_stats_ = envoy_container_stats_reader_->getEnvoyContainerStats();
}

void EnvoyContainerCpuUtilizationMonitor::updateResourceUsage(Server::ResourceUpdateCallbacks& callbacks) {
Copy link
Contributor

Choose a reason for hiding this comment

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

If we did the folding this file would effectively merge with the existing https://github.com/envoyproxy/envoy/blob/main/source/extensions/resource_monitors/cpu_utilization/cpu_utilization_monitor.h which it looks to me to be very similar.

@KBaichoo
Copy link
Contributor

/wait

nix1n added 2 commits January 1, 2025 14:06
Signed-off-by: nix1n <nikhil.murari@hotstar.com>
Signed-off-by: nix1n <nikhil.murari@hotstar.com>
nix1n and others added 3 commits January 6, 2025 15:06
Signed-off-by: nix1n <nikhil.murari@hotstar.com>
Signed-off-by: nix1n <nikhil.murari@hotstar.com>
@@ -13,7 +14,16 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE;
// [#protodoc-title: CPU utilization]
// [#extension: envoy.resource_monitors.cpu_utilization]

// The CPU utilization resource monitor reports the Envoy process the CPU Utilization of the entire host.
// The CPU utilization resource monitor reports the Envoy process the,
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm having trouble parsing this sentence. Can you please clarify?

Copy link
Contributor

@KBaichoo KBaichoo left a comment

Choose a reason for hiding this comment

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

Thanks for working on simplifying this, this looks much better.

/wait

@@ -22,13 +22,26 @@ struct CpuTimes {
uint64_t total_time;
};

struct CgroupStats {
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the reason we can't use the existing cpu times structure given it has effectively the same fields?

This would simplify the utilization monitor as it could then just use the CpuStatsReader interface vs the concrete implementation class.

Copy link
Author

Choose a reason for hiding this comment

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

@KBaichoo actually, for Host CPU Utilization, we just need /proc/stat file to read cpu_work and cpu_times both of the field in CpuTimes.
While to calculate container cpu utilization we need to read two different files to read allocated quota at some time, usage seconds total in nanoseconds at that time, and the time_difference from the timer only I could find to calculate. This method we are using in our ambassador edge stack service also, but to update injected resource pressure from a parallely running python script using same calculation strategy.

ref: google/cadvisor#2026 (comment)

So I created another class to read cgroup stats, which read allocated quota, total cpu time in nanoseconds.

This can be merged with CpuTimes class itself but not sure what meaningful naming would suffice for these two though both are uint64_t data type only. But yeah then the reader class also would need to access config mode and based on config mode it should calculate and return stats accordingly.

Copy link
Author

Choose a reason for hiding this comment

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

Time difference can be calculated by timeSource only in case of cgroup metrics . Which we don't need while calculating usage of host from /proc/stat file since all the data in this is time dependent already.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is a good point, if we were to push down the time source into the cgroup stats reader (along with some other logic) we would be able to produce the same format for CpuTimes -- e.g. https://github.com/envoyproxy/envoy/pull/37792/files#diff-1183c2c3937672e9d4c85d700d1e54ef13d360b4b517a0c643a8e46c13c3eb79R120

we could then de-dup a lot of the implementation details from being brought up at the monitor layer to avoid the monitoring layer having to have implementations for all of the different readers.

Copy link
Author

Choose a reason for hiding this comment

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

@KBaichoo in that case, in cgroupstatsreader, I have to derive some calculation to incorporate timing also to allocated millicores and usage seconds total metrics to make it equivalent to , cputimes fields total time and work time ? Something like that you are meaning ? And then the resource monitor would still using the original strategy without checking config mode and switching the calculation strategy?

Copy link
Author

Choose a reason for hiding this comment

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

This is a good point, if we were to push down the time source into the cgroup stats reader (along with some other logic) we would be able to produce the same format for CpuTimes -- e.g.

have checked the calculation strategy, It should work. But trying now to pushdown timesource from context in stats reader.

Copy link
Author

Choose a reason for hiding this comment

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

@KBaichoo /proc/uptime has precision till 100th of a second. We can use this without using timesource if refresh_interval is not selected below 0.01 seconds. But where to set it's limit ? How much less refresh_interval we can set ? Is it given ? We use 5 second of refresh_interval for loadshedding in our ambassador edgestack. Will there be any usecase that devs would use this for even less than 0.01 second refresh interval ?

Copy link
Author

Choose a reason for hiding this comment

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

Using proc/uptime metrics to calculate will get us rid of timesource in reader class and much simpler implementaion. Just with 1 limitation, refresh_interval less than 0.01 second won't work properly, but might update resource pressure as soon as the monitor's loop interval crosses 0.01 second. For refresh_interval greater than 0.01 seconds, it would be perfect. Please let me know if you will allow this. For us it should be sufficient.

Copy link
Author

Choose a reason for hiding this comment

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

Have implemented all tests and functionality using proc/uptime. @KBaichoo . This should be sufficient for us.

Copy link
Contributor

Choose a reason for hiding this comment

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

I might be missing something, why do we need to use uptime vs the timesource? ISTM it might be cheaper to use timesource e.g. no file open, etc. and no limitation on uptime granularity.

100% agree that for most use cases polling resource monitors faster is a great way to eat up CPU.

@@ -20,6 +22,16 @@ class LinuxCpuStatsReader : public CpuStatsReader {
const std::string cpu_stats_filename_;
};

class LinuxContainerCpuStatsReader: public CgroupStatsReader {
Copy link
Contributor

Choose a reason for hiding this comment

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

as mentioned above in https://github.com/envoyproxy/envoy/pull/37792/files#diff-9281e66aafccb8196311602044a32a4ac53a877d7bae55cc591df8f30ae15810R25 if we go that route this can then just inherit directly from CpuStatsReader interface.

Copy link
Author

Choose a reason for hiding this comment

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

@KBaichoo have got rid of timesource and removed multiple redundancies and simplified the monitor as well , our org would not use refresh_interval smaller than even 1 second. And envoy's cpu overhead again increases if we use more smaller refresh_interval . So sticking with /proc/uptime stats which has precision of 0.01 second. Which is more than enough for most usecases and from now on linux cpu stats reader will be sending stats to monitor based on the strategy configured.

Signed-off-by: nix1n <nikhil.murari@hotstar.com>
Signed-off-by: nix1n <nikhil.murari@hotstar.com>
Copy link
Contributor

@KBaichoo KBaichoo left a comment

Choose a reason for hiding this comment

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

/wait

@@ -35,7 +35,7 @@ class CpuUtilizationMonitor : public Server::ResourceMonitor {
std::unique_ptr <CgroupStatsReader> cgroup_stats_reader_;
TimeSource& time_source_;
MonotonicTime last_update_time_;
envoy::extensions::resource_monitors::cpu_utilization::v3::CpuUtilizationConfig_UtilizationComputeStrategy mode_;
int16_t mode_ = -1; // Will be updated in Resource Monitor Class Constructor
Copy link
Contributor

Choose a reason for hiding this comment

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

to be more specific I meant to use the non-mangled named like:

envoy::extensions::resource_monitors::cpu_utilization::v3::CpuUtilizationConfig::UtilizationComputeStrategy

Copy link
Author

Choose a reason for hiding this comment

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

I did try this earlier already, it won't build. Previous two ways were only compiling successfully for me. If we use this we have to use default in switch statement then only it builds which you said to avoid.
image

Copy link
Author

Choose a reason for hiding this comment

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

image

Copy link
Author

Choose a reason for hiding this comment

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

we can try something like this then, container is specific mode. rest for default computeHostUsage()
image

@@ -22,13 +22,26 @@ struct CpuTimes {
uint64_t total_time;
};

struct CgroupStats {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a good point, if we were to push down the time source into the cgroup stats reader (along with some other logic) we would be able to produce the same format for CpuTimes -- e.g. https://github.com/envoyproxy/envoy/pull/37792/files#diff-1183c2c3937672e9d4c85d700d1e54ef13d360b4b517a0c643a8e46c13c3eb79R120

we could then de-dup a lot of the implementation details from being brought up at the monitor layer to avoid the monitoring layer having to have implementations for all of the different readers.

Signed-off-by: nix1n <nikhil.murari@hotstar.com>
nix1n and others added 6 commits January 8, 2025 17:41
Signed-off-by: nix1n <138643332+nix1n@users.noreply.github.com>
Signed-off-by: nix1n <nikhil.murari@hotstar.com>
Signed-off-by: nix1n <nikhil.murari@hotstar.com>
Signed-off-by: nix1n <nikhil.murari@hotstar.com>
Signed-off-by: nix1n <nikhil.murari@hotstar.com>
Signed-off-by: nix1n <nikhil.murari@hotstar.com>
Copy link
Author

@nix1n nix1n left a comment

Choose a reason for hiding this comment

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

/ready

nix1n and others added 4 commits January 9, 2025 14:48
Signed-off-by: nix1n <nikhil.murari@hotstar.com>
Signed-off-by: nix1n <nikhil.murari@hotstar.com>
Signed-off-by: nix1n <nikhil.murari@hotstar.com>
Copy link
Contributor

@KBaichoo KBaichoo left a comment

Choose a reason for hiding this comment

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

This is looking much better and almost ready to land. Thank you for iterating on this @nix1n!

@@ -13,7 +14,16 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE;
// [#protodoc-title: CPU utilization]
// [#extension: envoy.resource_monitors.cpu_utilization]

// The CPU utilization resource monitor reports the Envoy process the CPU Utilization of the entire host.
// Today, this only works on Linux and is calculated using the stats in the /proc/stat file.
// The CPU utilization resource monitor reports the Envoy process the, CPU Utilization
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you fix the grammar in this sentence, something like:
The CPU utilization resource monitors and reports CPU usage across different platforms.

Copy link
Author

Choose a reason for hiding this comment

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

done

@@ -419,6 +419,10 @@ new_features:
change: |
Add the option to reduce the rate limit budget based on request/response contexts on stream done.
See :ref:`apply_on_stream_done <envoy_v3_api_field_config.route.v3.RateLimit.apply_on_stream_done>` for more details.
- area: resource_monitors
change: |
Added support for to monitor Container CPU utilization in Linux K8s environment using existing
Copy link
Contributor

Choose a reason for hiding this comment

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

s/for//

Copy link
Author

Choose a reason for hiding this comment

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

done

@@ -366,7 +366,9 @@ this overload action can be used to ensure the fleet does not get into a cascadi
mode.
Some platform owners may choose to install this overload action by default to protect the fleet,
since it is easier to configure a target CPU utilization percentage than to configure a request rate per
workload.
workload. This supports monitoring both HOST CPU Utilization and K8s Container CPU Utilization.
By default it's using mode: HOST , to trigger overload actions on Container CPU usage,
Copy link
Contributor

Choose a reason for hiding this comment

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

s/HOST ,/HOST,/.

Copy link
Author

Choose a reason for hiding this comment

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

done


CpuTimes LinuxCpuStatsReader::getCpuTimes() {
if (mode_ ==
Copy link
Contributor

Choose a reason for hiding this comment

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

I see you used kind of the "strategy" pattern here, I think we could simplify this by having two different implementations since e.g.

LinuxHostCpuStatReader which inherits and implement CpuStatsReader and
LinuxContainerCpuStatsReader which inherits and implements CpuStatsReader.

The implementations would just be getCpuTimes having the corresponding code of getHostCpuTimes or getContainerCpuTimes and we'd avoid the check every time -- instead just doing the check once in the factor for making the cpu utilization monitor.

The other nice part about that is that only the relevant files would need to be provided to the different implementations.

Since both return the same underlying CPUTimes and the CPU Utilization monitor uses the interface to interact with them there shouldn't be any friction there.

Copy link
Author

Choose a reason for hiding this comment

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

Done and got the timesource implemented in reader itself.

@@ -22,13 +22,26 @@ struct CpuTimes {
uint64_t total_time;
};

struct CgroupStats {
Copy link
Contributor

Choose a reason for hiding this comment

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

I might be missing something, why do we need to use uptime vs the timesource? ISTM it might be cheaper to use timesource e.g. no file open, etc. and no limitation on uptime granularity.

100% agree that for most use cases polling resource monitors faster is a great way to eat up CPU.

uint64_t work_time;
uint64_t total_time;
double work_time;
double total_time;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why did we need to switch these to doubles?

Copy link
Author

Choose a reason for hiding this comment

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

Updated calculation strategy, Now it's just
double work_time, [ This is due to division operation by allocated cpu millicores for averaging. So for container mode, it will divide that's why to accomodate the calculation. ]
and total_time is uint64_t again,
, and since now I have incorporated timesource within LinuxContainerCpuStatsReader, we can return time in nanoseconds as integer itself. Previously it was using proc/uptime so to accomodate it's 1/100th precision it was double. Have changed it back to uint64_t.
So now it's
double work_time;
uint64_t total_time;

Can you get it released soon ? @KBaichoo .

Copy link
Author

Choose a reason for hiding this comment

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

Will there be a problem in merging it if coverage test is failing but for other extension. @KBaichoo

Per-extension coverage failed:
Code coverage for source/common/quic is lower than limit of 93.4 (93.3)
Yesterday it was fine.

@KBaichoo
Copy link
Contributor

KBaichoo commented Jan 9, 2025

/wait

nix1n added 7 commits January 10, 2025 11:41
Signed-off-by: nix1n <nikhil.murari@hotstar.com>
Signed-off-by: nix1n <nikhil.murari@hotstar.com>
Signed-off-by: nix1n <nikhil.murari@hotstar.com>
Signed-off-by: nix1n <nikhil.murari@hotstar.com>
Signed-off-by: nix1n <nikhil.murari@hotstar.com>
Signed-off-by: nix1n <nikhil.murari@hotstar.com>
Signed-off-by: nix1n <nikhil.murari@hotstar.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants