-
Notifications
You must be signed in to change notification settings - Fork 760
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
feat: improved cross platform metric collection #2834
base: master
Are you sure you want to change the base?
Conversation
hey all, thanks for agreeing to take a look. I havent done much OSS before so im looking forward to hearing your thoughts :). Assuming it all looks good to you, when might it be incorperated into a crawlee release? At work we have a project that hinged on using crawlee in k8 so the autoscaling issues in containers is causing a fairly significant issue for us. When you come to review it, id be happy to hop on a discord call and discuss it. :) Thanks for everything you do! |
Hi @NathanSavageKaimai and thanks for your willingness to contribute! In the issue that this aims to close, you mentioned the possibility of using the |
hi @janbuchar, ps-list would serve the same purpose as the new Let me know if you would like me to explore these options. |
with ps-list, you are relying on a third party binary which doesnt provide its source code as far as i can tell. It could be a potential supply chain risk. |
I will +1 that worry, I'm not a fan of using a dependency that embeds a binary whose source code isn't directly open source / one we could build ourselves and embed |
Um, as far as I can tell, |
@janbuchar Ah yep, i hadnt found the cpp repo. still, being externally tracked, theres no automatic method to verify the authenticity of the binary beyond downloading from both sources and checking the hashes. |
The fact the binary is just embedded in instead of precompiled (like impit) or built at install time is a worry imo |
You both make a good point. I'd still consider exploring |
cool will do. :) |
@janbuchar ive had a play around with |
Sounds reasonable, thanks! I will try to review the code in depth this week. |
f7d7518
to
173630c
Compare
hey @janbuchar have you been able to have a look yet? No worries ethier way. if you like, we can sit down for a call later. Just shoot me a message on Discord - crafty5064. Im free until 2pm UTC or all day Saturday. :) |
Hi all, I hope you had a good weekend! Have you had a chance to review these changes? I was hoping they might be included in a release soon as these issues are blocking my company from deploying our product fully to k8. If you would like a chat, im free untill 1pm utc tomorrow. :) |
Hi, I'll look into it tomorrow. Sorry for the delay! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good job on this one! I have a bunch of readability/code structure comments. More importantly though, the tests here are very narrowly scoped and use mocking heavily. Do you think you could add an E2E test that would verify that the system information detection works as expected? Feel free to suggest any other way to test this as a whole.
@@ -43,6 +43,35 @@ export async function isDocker(forceReset?: boolean): Promise<boolean> { | |||
return isDockerPromiseCache; | |||
} | |||
|
|||
/** | |||
* Returns a `Promise` that resolves to true if the code is running in a containerised environment. | |||
* Returns true if the CRAWLEE_CONTAINERISED environment variable is set. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Who is supposed to set the CRAWLEE_CONTAINERISED
environment variable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that is meant to be a manual way to run the containerised resource checks in case the other heuristics dont catch it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it. Could you add it to the documentation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would you mind showing me where? im a little bit lost on that side of it, ta.
Im free for a call right now if you would like a chat. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand that 😁 https://github.com/apify/crawlee/blob/master/docs/guides/configuration.mdx this is the place
* @param includeRoot - Optional flag. When true, include the process with the given PID if found. | ||
* Defaults to false. | ||
*/ | ||
export async function psTree(pid: number | string, includeRoot: boolean = false): Promise<ProcessInfo[]> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a very long and complex function. Possibly the main reason why it's hard to read is that it combines the implementation for UNIX and Windows in a single function. Could it be broken down into multiple smaller functions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it was pretty much a copy paste from apify/pstree with the WMIC changes. I can reformat it. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please do that, apify/pstree is super dated and I'm sure that if we don't refactor it now, we won't get back to it, ever.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cool will do :)
hi @janbuchar thanks for your insight! Most of the points were just me trying to follow conventions set out in the prexisting memory-info.ts file but i will definitely work on clarifying it. :) |
@NathanSavageKaimai please look into refactoring of Also, any thoughts regarding this?
|
its a difficult one given that its so close to the metal, an e2e test would be dependant on the current state of the test runner unless i mocked the exec call and readline interface but at that point it may as well be a unit test. Also, personally I am only set up to run tests on windows or linux through wsl so i cant verify Macos compatability beyond "its a copy paste from a solution that persumably worked". What i will do is reimplement the unit tests in apify/pstree |
Cool, that will at least give us some certainty that I guess we could make a script that would show the current CPU and memory usage ratio and compare it with the old implementation. Then we could at least test-drive this on several machines with different OS and see if it behaves reasonably. What do you think? |
Hi @NathanSavageKaimai, we discussed this PR with @B4nan. Since this change impacts critical parts of Crawlee's functionality and testing it deeply enough requires time that we don't currently have (taking into account that you need this released soon), would you be open to making the new system info implementation opt-in using some kind of a feature flag? If yes, we could probably release it and test it later, before we decide to make the new implementation the default. |
Hi @janbuchar, sounds good, do you want me to make it an experiment? Also, any preference on what i call the flag? Ta |
Yup, |
… 'systemInfoV2' experiment feature flag
c849613
to
ae4a502
Compare
The original metric collection has been restored and the new solution made toggleable with the systemInfoV2 experimental feature flag. I ended up needing to add the systemInfoV2 flag to the configuration class as |
hi, just been doing some more tests and i found an issue, please dont merge yet. ta |
…and 1 fixed implementation and test
hi again, @janbuchar. I have fixed the scaling error. If you are all happy about it, perhaps we could merge it in soon? Thanks. :) |
Perfect! I think there are no major issues. We agreed with @B4nan that he'll quickly scan it and, if everything is fine, merge it. In the meantime, I'll try and test some risky changes we have waiting in the release branch so that we can make a stable release. Thanks again for your contribution and for bearing with us 🙂 |
sounds good, just one more thing i have noticed, currently, the "TICKS_PER_SECOND" is hardcoded to 100, im going to introduce a quick check to read it from the kernel as i have found out that in some systems, rarely it can be different. |
hi @B4nan, thanks for running the pull request toolkit, have you been able to have a look over the changes? No worries ethier way. :) |
Hi @janbuchar have you been able to talk with @B4nan? my boss is asking for a timeline. Ta |
You will need to wait a bit, the PR is large, so it takes time. If you need to adopt this in your own project asap, I'd suggest you use something like https://github.com/ds300/patch-package instead of pushing us for merging it early. |
Apologies, i didnt mean to offend. My understanding of the situation was that you were happy with janbuchar's review and you were going to simply scan it. Thanks for all you are doing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
left a few comments, haven't seen the whole thing yet, but it's looking pretty good. i'd remove the experiments option in favor of the configuration one, having both of them feels weird, this is surely a system-wide option, so the config class is more appropriate for it
/** | ||
* Enables the use of the new resource management system. | ||
* It should improve autoscaling in containerized environments by respecting cGroup resource limits. | ||
*/ | ||
systemInfoV2?: boolean; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's keep only the configuration option, it's weird to have two feature flags for one thing
test/utils/memory-infoV2.test.ts
Outdated
// TODO: check if this comment is still accurate | ||
// this test hangs because we launch the browser, closing is apparently not enough? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i guess this is not relevant anymore, given the test doens't hang?
test/utils/memory-infoV2.test.ts
Outdated
// TODO: check if this comment is still accurate | ||
// this test hangs because we launch the browser, closing is apparently not enough? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
@@ -15,6 +15,8 @@ export const URL_NO_COMMAS_REGEX = | |||
export const URL_WITH_COMMAS_REGEX = | |||
/https?:\/\/(www\.)?([\p{L}0-9]|[\p{L}0-9][-\p{L}0-9@:%._+~#=]{0,254}[\p{L}0-9])\.[a-z]{2,63}(:\d{1,5})?(\/[-\p{L}0-9@:%_+,.~#?&/=()]*)?/giu; | |||
|
|||
export const FALSY_REGEX = /^false$/giu; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we really need to export this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not really, i just did that since the other regexes were exported
log.exception(err as Error, 'Cpu snapshot failed.'); | ||
return {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this a good idea? downstream will expect values, and end up working with undefined
instead
unless there is a good reason, i would remove the try/catch here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will do, i put that for parity with the existing createMemoryInfo function
* @returns a number between 0 and 1 for the cpu load | ||
*/ | ||
export async function getCurrentCpuTicksV2(): Promise<number> { | ||
if (await isContainerized()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
check for the negation and return early so the huge nested block doesnt end up nested
if (this.config.get('systemInfoV2')) { | ||
const usedCpuRatio = await getCurrentCpuTicksV2(); | ||
return { | ||
cpuCurrentUsage: usedCpuRatio * 100, | ||
isCpuOverloaded: usedCpuRatio > options.maxUsedCpuRatio, | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
only this part needs to be in the try/catch if we want to keep it. but i would rather not have it here, if we want to have it somewhere, why not have it inside the getCurrentCpuTicksV2
method and return some meaningful defaults instead?
} | ||
|
||
private async createMemoryInfo() { | ||
try { | ||
const memInfo = await this._getMemoryInfo(); | ||
let memInfo = { mainProcessBytes: -1, childProcessesBytes: -1 }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
whats the point of the default value here? its always overridden
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it was to properly scope the variable since memInfo is used in the same manner by v1 and v2, i will reformat
if (process.env.CRAWLEE_CONTAINERIZED) { | ||
isContainerizedResult = !FALSY_REGEX.test(process.env.CRAWLEE_CONTAINERIZED); | ||
return isContainerizedResult; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ideally this would be handled via configuration class too, which normalizes env vars
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unfortunately, we cannot access the Configuration class in utils, it would create a circular dependancy. I could refactor it into core?
hi @B4nan, working on those changes now. If we only want the configuration feature flag and not the experiment one, do you still want the experiment documentation page or shoudl i move it somewhere else? Ta |
yeah, let's keep the docs, that's fine |
- removed systemInfoV2 experiment flag in favour of config flag - added containerized / CRAWLEE_CONTAINERIZED config flag - isContainerized no longer checks the 'containerized' flag, instead 'containerized' is used in place of isContainerized in LocalEventManager
This PR improves CPU and RAM metric collection across multiple environments. The CPU metrics are now fully cGroup aware report properly in containerised environments with cpu quota limits. The memory profile method on windows has been updated from using legacy WMIC to powershell. Finally two new utility methods have been added to
@crawlee/utils
,general.ts
to determine if the scraper is containerised (instead of just running in docker) as well as if cGroup is enabled.Adds
@crawlee/utils
general.ts
isContainerised()
an extention ofisDocker()
that also checks for the presence of aKUBERNETES_SERVICE_HOST
environment variable for k8 and aCRAWLEE_CONTAINERISED
environment variable for manual control.getCgroupsVersion()
a method to determine the cGroup version in a cGroup controlled environment. It does this by checking for a file at/sys/fs/cgroup/memory/
. If it is present, the cGroup verison is 1, else version 2.cpu-info.ts
Collects cpu infomation in a similar manner to
memory-info.ts
getCurrentCpuTicks()
The existing solution. Used in AWS lambda, containerised environments without a cGroup cpu limit and on bare metal.getCpuQuota()
Gets the cpu quota in cGroup controlled environments.getCpuPeriod()
Gets the cpu quota period in cGroup controlled environments.getContainerCpuUsage()
Gets the containers cpu usage.getSystemCpuUsage()
Gets the systems cpu usage.getCpuInfo()
The main method for collecting cpu load metrics. Determines the enviroment and calls the other functions accordingly.Removes
@apify/ps-tree
A legacy package that checked memory usage using
WMIC.exe
(depreciated) on windows orps
on *nix. Replaced by@crawlee\packages\utils\src\internals\psTree.ts
which calculates the memory usage in a similar manner but usingpowershell
andGet-CimInstance Win32_Process
. Also adds type safety.Fixes
Fixes: #2771