Feedback, loop.load() function
![](https://secure.gravatar.com/avatar/cf278a5382dde75bd7391751754e80b3.jpg?s=120&d=mm&r=g)
Hi guys, I would appreciate any feedback about the idea of implementing a new load function to ask about how saturated is your reactor. I have a proof of concept [1] of how the load function might be implemented in the Asyncio python loop. The idea is to provide a method that can be used to ask about the load of the reactor in a specific time, this implementation returns the load taking into account the last 60 seconds but it can easily return the 5m and 15minutes ones u others. This method can help services built on to of Asyncio to implement back pressure mechanisms that take into account a metric coming from the loop, instead of inferring the load using other metrics provided by external agents such as the CPU, load average u others. Nowadays exists some alternatives for other languages that address this situation using the lag of a scheduler callback, produced by saturated reactors. The most known implementation is toobusy [2] a nodejs implementation. IMHO the solution provided by tobusy has a strong dependency with the hardware needing to tune the maximum lag allowed in terms of milliseconds [3]. in the POF presented the user can use an exact value meaning the percentage of the load, perhaps 0.9 Any comment would be appreciated. [1] https://github.com/pfreixes/cpython/commit/5fef3cae043abd62165ce40b181286e18... [2] https://www.npmjs.com/package/toobusy [3] https://www.npmjs.com/package/toobusy#tunable-parameters -- --pau
![](https://secure.gravatar.com/avatar/9b513ee7cc400c9b7374f4697a1e69ad.jpg?s=120&d=mm&r=g)
On Sun, Aug 6, 2017 at 3:57 PM, Pau Freixes <pfreixes@gmail.com> wrote:
Hi guys,
I would appreciate any feedback about the idea of implementing a new load function to ask about how saturated is your reactor.
Hi, Would it be possible for you to rephrase what you've done in terms of asyncio terminology? From what I can tell, "reactor" isn't a term used in the asyncio docs or code base. It might also improve the readability of your asyncio patch to use asyncio terminology in the code comments, doc strings, etc. --Chris
I have a proof of concept [1] of how the load function might be implemented in the Asyncio python loop.
The idea is to provide a method that can be used to ask about the load of the reactor in a specific time, this implementation returns the load taking into account the last 60 seconds but it can easily return the 5m and 15minutes ones u others.
This method can help services built on to of Asyncio to implement back pressure mechanisms that take into account a metric coming from the loop, instead of inferring the load using other metrics provided by external agents such as the CPU, load average u others.
Nowadays exists some alternatives for other languages that address this situation using the lag of a scheduler callback, produced by saturated reactors. The most known implementation is toobusy [2] a nodejs implementation.
IMHO the solution provided by tobusy has a strong dependency with the hardware needing to tune the maximum lag allowed in terms of milliseconds [3]. in the POF presented the user can use an exact value meaning the percentage of the load, perhaps 0.9
Any comment would be appreciated.
[1] https://github.com/pfreixes/cpython/commit/5fef3cae043abd62165ce40b181286e18... [2] https://www.npmjs.com/package/toobusy [3] https://www.npmjs.com/package/toobusy#tunable-parameters -- --pau _______________________________________________ Async-sig mailing list Async-sig@python.org https://mail.python.org/mailman/listinfo/async-sig Code of Conduct: https://www.python.org/psf/codeofconduct/
![](https://secure.gravatar.com/avatar/cf278a5382dde75bd7391751754e80b3.jpg?s=120&d=mm&r=g)
Would it be possible for you to rephrase what you've done in terms of asyncio terminology? From what I can tell, "reactor" isn't a term used in the asyncio docs or code base.
s/reactor/loop, in any case, I will take into account to make all of my comments consistency with the current Python documentation.
It might also improve the readability of your asyncio patch to use asyncio terminology in the code comments, doc strings, etc.
Just a POC to gather some feedbacks. But appreciate your comment, I will take into account for new code. -- --pau
![](https://secure.gravatar.com/avatar/97c543aca1ac7bbcfb5279d0300c8330.jpg?s=120&d=mm&r=g)
It looks like your "load average" is computing something very different than the traditional Unix "load average". If I'm reading right, yours is a measure of what percentage of the time the loop spent sleeping waiting for I/O, taken over the last 60 ticks of a 1 second timer (so generally slightly longer than 60 seconds). The traditional Unix load average is an exponentially weighted moving average of the length of the run queue. Is one of those definitions better for your goal of detecting when to shed load? I don't know. But calling them the same thing is pretty confusing :-). The Unix version also has the nice property that it can actually go above 1; yours doesn't distinguish between a service whose load is at exactly 100% of capacity and barely keeping up, versus one that's at 200% of capacity and melting down. But for load shedding maybe you always want your tripwire to be below that anyway. More broadly we might ask what's the best possible metric for this purpose – how do we judge? A nice thing about the JavaScript library you mention is that scheduling delay is a real thing that directly impacts quality of service – it's more of an "end to end" measure in a sense. Of course, if you really want an end to end measure you can do things like instrument your actual logic, see how fast you're replying to http requests or whatever, which is even more valid but creates complications because some requests are supposed to take longer than others, etc. I don't know which design goals are important for real operations. On Aug 6, 2017 3:57 PM, "Pau Freixes" <pfreixes@gmail.com> wrote:
Hi guys,
I would appreciate any feedback about the idea of implementing a new load function to ask about how saturated is your reactor.
I have a proof of concept [1] of how the load function might be implemented in the Asyncio python loop.
The idea is to provide a method that can be used to ask about the load of the reactor in a specific time, this implementation returns the load taking into account the last 60 seconds but it can easily return the 5m and 15minutes ones u others.
This method can help services built on to of Asyncio to implement back pressure mechanisms that take into account a metric coming from the loop, instead of inferring the load using other metrics provided by external agents such as the CPU, load average u others.
Nowadays exists some alternatives for other languages that address this situation using the lag of a scheduler callback, produced by saturated reactors. The most known implementation is toobusy [2] a nodejs implementation.
IMHO the solution provided by tobusy has a strong dependency with the hardware needing to tune the maximum lag allowed in terms of milliseconds [3]. in the POF presented the user can use an exact value meaning the percentage of the load, perhaps 0.9
Any comment would be appreciated.
[1] https://github.com/pfreixes/cpython/commit/ 5fef3cae043abd62165ce40b181286e18f5fb19c [2] https://www.npmjs.com/package/toobusy [3] https://www.npmjs.com/package/toobusy#tunable-parameters -- --pau _______________________________________________ Async-sig mailing list Async-sig@python.org https://mail.python.org/mailman/listinfo/async-sig Code of Conduct: https://www.python.org/psf/codeofconduct/
![](https://secure.gravatar.com/avatar/cf278a5382dde75bd7391751754e80b3.jpg?s=120&d=mm&r=g)
It looks like your "load average" is computing something very different than the traditional Unix "load average". If I'm reading right, yours is a measure of what percentage of the time the loop spent sleeping waiting for I/O, taken over the last 60 ticks of a 1 second timer (so generally slightly longer than 60 seconds). The traditional Unix load average is an exponentially weighted moving average of the length of the run queue.
The implementation proposed wants to expose the load of the loop. Having a direct metric that comes from the loop instead of using an external metric such as CPU, load average u others. Yes, the load average uses a decay function based on the length of the run queue for those processes that are using or waiting for a CPU, this gives us extra information about how overloaded is our system. If you compare it with the CPU load. In the case presented, the load of the loop is something equivalent with the load of the CPU and it does not have the ability to inform you about how much overloaded is your loop once reached the 100%.
Is one of those definitions better for your goal of detecting when to shed load? I don't know. But calling them the same thing is pretty confusing :-). The Unix version also has the nice property that it can actually go above 1; yours doesn't distinguish between a service whose load is at exactly 100% of capacity and barely keeping up, versus one that's at 200% of capacity and melting down. But for load shedding maybe you always want your tripwire to be below that anyway.
Well, I partially disagree with this. The load definition has its equivalent in computing with other metrics that have a close range, such as the CPU one. I've never had the intention to align the load of the loop with the load average, I've just used the concept as an example of the metric that might be used to check how loaded is your system.
More broadly we might ask what's the best possible metric for this purpose – how do we judge? A nice thing about the JavaScript library you mention is that scheduling delay is a real thing that directly impacts the quality of service – it's more of an "end to end" measure in a sense. Of course, if you really want an end to end measure you can do things like instrument your actual logic, see how fast you're replying to HTTP requests or whatever, which is even more valid but creates complications because some requests are supposed to take longer than others, etc. I don't know which design goals are important for real operations.
Here the key for me, something where I should have based my rationale. How good is the way presented to measure a load of your asynchronous system compared with the toobusy one? what can we achieve with this metric? I will work on that as the base of my rationale for the change proposed. Then, once if the rationale is accepted the implementation is peanuts :) -- --pau
![](https://secure.gravatar.com/avatar/cf278a5382dde75bd7391751754e80b3.jpg?s=120&d=mm&r=g)
Hi, I have a second implementation of the load function [1], the previous one was too much naive. The main changes compared with the previous one are: 1) Uses a decay function instead of a vector of samples. 2) The load stands for the percentage of CPU used, giving a global view of how many CPU resources are still unused. 3) The update is done at each _LOAD_FREQ - default 1 second - without using a scheduler callback 4) Many corner cases fixed The performance impact introduced in the default loop implemented with Python is approx 3% running a trivial program that does not have an application overhead [2]. Therefore, in real applications with at least some footprint introduced by the application, this performance impact should be negligible. As an example of how the load method can be used, the following code [3] runs the loop using different ratios of coroutines per second, where each coroutine has a CPU impact of 0.02. Having a maximum throughput expected of 50 coroutines per second. In this example, the coroutine asks first for the load of the system before start consuming the CPU, if the load is higher than 0.9 the coroutine leaves doing nothing. The following snippet shows the execution output : Load reached for 10.0 coros/seq: 0.20594804872227002, abandoned 0/100 reseting load.... Load reached for 20.0 coros/seq: 0.40599215789994814, abandoned 0/200 reseting load.... Load reached for 40.0 coros/seq: 0.8055964270483202, abandoned 0/400 reseting load.... Load reached for 80.0 coros/seq: 0.9390106831339007, abandoned 450/800 The program runs as was said different levels of throughput printing at the final of each one the load of the system reached, and the coroutines that were abandoned vs the overall. As can be seen, once the last test begins to run and the load of the system reaches the 0.90 it is able to reduce the pressure at the application level, in that case just leaving doing nothing but in other environments doing the proper fallback. As you can notice the load values seem to be aligned with the values expected, having in mind that the maximum throughput reached would be 50 coroutines per second. The current implementation tries to return the load taking into account the global CPU resources, therefore other processes impacting on the use of the same CPU used by the loop should be considered. It would give to the developer a reliable metric that can be used in environments where the CPU is shared by other processes. What is missing? Investigate how difficult will be implemented this feature in libuv [4] to make it available in uvloop. Give more information about the difference between this implementation vs the toobusy one [5]. Test performance impact in real applications. I will like to get more feedback from you. And, if you believe that this implementation has some chances to be part of CPython repo which would be the next steps that I should make. [1] https://github.com/pfreixes/cpython/commit/ac07fef5af51746c7311494f21b0f067c... [2] https://gist.github.com/pfreixes/233fd8c6a6ec82f2cde4688a2976bf2d [3] https://gist.github.com/pfreixes/fd26c36391b33056b7efd525e4690aef [4] http://docs.libuv.org/en/v1.x/ [5] https://github.com/lloyd/node-toobusy On Sun, Aug 13, 2017 at 12:54 PM, Pau Freixes <pfreixes@gmail.com> wrote:
It looks like your "load average" is computing something very different than the traditional Unix "load average". If I'm reading right, yours is a measure of what percentage of the time the loop spent sleeping waiting for I/O, taken over the last 60 ticks of a 1 second timer (so generally slightly longer than 60 seconds). The traditional Unix load average is an exponentially weighted moving average of the length of the run queue.
The implementation proposed wants to expose the load of the loop. Having a direct metric that comes from the loop instead of using an external metric such as CPU, load average u others.
Yes, the load average uses a decay function based on the length of the run queue for those processes that are using or waiting for a CPU, this gives us extra information about how overloaded is our system. If you compare it with the CPU load.
In the case presented, the load of the loop is something equivalent with the load of the CPU and it does not have the ability to inform you about how much overloaded is your loop once reached the 100%.
Is one of those definitions better for your goal of detecting when to shed load? I don't know. But calling them the same thing is pretty confusing :-). The Unix version also has the nice property that it can actually go above 1; yours doesn't distinguish between a service whose load is at exactly 100% of capacity and barely keeping up, versus one that's at 200% of capacity and melting down. But for load shedding maybe you always want your tripwire to be below that anyway.
Well, I partially disagree with this. The load definition has its equivalent in computing with other metrics that have a close range, such as the CPU one. I've never had the intention to align the load of the loop with the load average, I've just used the concept as an example of the metric that might be used to check how loaded is your system.
More broadly we might ask what's the best possible metric for this purpose – how do we judge? A nice thing about the JavaScript library you mention is that scheduling delay is a real thing that directly impacts the quality of service – it's more of an "end to end" measure in a sense. Of course, if you really want an end to end measure you can do things like instrument your actual logic, see how fast you're replying to HTTP requests or whatever, which is even more valid but creates complications because some requests are supposed to take longer than others, etc. I don't know which design goals are important for real operations.
Here the key for me, something where I should have based my rationale. How good is the way presented to measure a load of your asynchronous system compared with the toobusy one? what can we achieve with this metric?
I will work on that as the base of my rationale for the change proposed. Then, once if the rationale is accepted the implementation is peanuts :)
-- --pau
-- --pau
![](https://secure.gravatar.com/avatar/60a2f1855ca0d8aac3fa75a57233a3f1.jpg?s=120&d=mm&r=g)
Hi Pau, I personally don't think we need this in asyncio. While the function has a relatively low overhead, it's still an overhead, it's a couple more syscalls on each loop iteration, and it's a bit of a technical debt. The bar for adding new functionality to asyncio is very high, and I don't see a good rationale for adding this function. Is it for debugging purposes? Or for profiling live applications? If it's the latter, then there are so many other things we want to see, and some of them are protocol-specific. If we want to add some tracing/profiling functions there should be a way to disable them, otherwise the performance of event loops like uvloop will degrade, and I'm not sure that all of its users want to pay a price for something they won't ever be using. All of this just adds to the complexity. OTOH, event loops in asyncio are pluggable. You can just subclass the asyncio event loop and add your method. asyncio code base is very stable, so you won't need to fix your code frequently. Thanks, Yury On Aug 20, 2017, 7:27 PM -0400, Pau Freixes <pfreixes@gmail.com>, wrote:
Hi,
I have a second implementation of the load function [1], the previous one was too much naive. The main changes compared with the previous one are:
1) Uses a decay function instead of a vector of samples. 2) The load stands for the percentage of CPU used, giving a global view of how many CPU resources are still unused. 3) The update is done at each _LOAD_FREQ - default 1 second - without using a scheduler callback 4) Many corner cases fixed
The performance impact introduced in the default loop implemented with Python is approx 3% running a trivial program that does not have an application overhead [2]. Therefore, in real applications with at least some footprint introduced by the application, this performance impact should be negligible.
As an example of how the load method can be used, the following code [3] runs the loop using different ratios of coroutines per second, where each coroutine has a CPU impact of 0.02. Having a maximum throughput expected of 50 coroutines per second. In this example, the coroutine asks first for the load of the system before start consuming the CPU, if the load is higher than 0.9 the coroutine leaves doing nothing.
The following snippet shows the execution output :
Load reached for 10.0 coros/seq: 0.20594804872227002, abandoned 0/100 reseting load.... Load reached for 20.0 coros/seq: 0.40599215789994814, abandoned 0/200 reseting load.... Load reached for 40.0 coros/seq: 0.8055964270483202, abandoned 0/400 reseting load.... Load reached for 80.0 coros/seq: 0.9390106831339007, abandoned 450/800
The program runs as was said different levels of throughput printing at the final of each one the load of the system reached, and the coroutines that were abandoned vs the overall. As can be seen, once the last test begins to run and the load of the system reaches the 0.90 it is able to reduce the pressure at the application level, in that case just leaving doing nothing but in other environments doing the proper fallback. As you can notice the load values seem to be aligned with the values expected, having in mind that the maximum throughput reached would be 50 coroutines per second.
The current implementation tries to return the load taking into account the global CPU resources, therefore other processes impacting on the use of the same CPU used by the loop should be considered. It would give to the developer a reliable metric that can be used in environments where the CPU is shared by other processes.
What is missing? Investigate how difficult will be implemented this feature in libuv [4] to make it available in uvloop. Give more information about the difference between this implementation vs the toobusy one [5]. Test performance impact in real applications.
I will like to get more feedback from you. And, if you believe that this implementation has some chances to be part of CPython repo which would be the next steps that I should make.
[1] https://github.com/pfreixes/cpython/commit/ac07fef5af51746c7311494f21b0f067c... [2] https://gist.github.com/pfreixes/233fd8c6a6ec82f2cde4688a2976bf2d [3] https://gist.github.com/pfreixes/fd26c36391b33056b7efd525e4690aef [4] http://docs.libuv.org/en/v1.x/ [5] https://github.com/lloyd/node-toobusy
On Sun, Aug 13, 2017 at 12:54 PM, Pau Freixes <pfreixes@gmail.com> wrote:
It looks like your "load average" is computing something very different than the traditional Unix "load average". If I'm reading right, yours is a measure of what percentage of the time the loop spent sleeping waiting for I/O, taken over the last 60 ticks of a 1 second timer (so generally slightly longer than 60 seconds). The traditional Unix load average is an exponentially weighted moving average of the length of the run queue.
The implementation proposed wants to expose the load of the loop. Having a direct metric that comes from the loop instead of using an external metric such as CPU, load average u others.
Yes, the load average uses a decay function based on the length of the run queue for those processes that are using or waiting for a CPU, this gives us extra information about how overloaded is our system. If you compare it with the CPU load.
In the case presented, the load of the loop is something equivalent with the load of the CPU and it does not have the ability to inform you about how much overloaded is your loop once reached the 100%.
Is one of those definitions better for your goal of detecting when to shed load? I don't know. But calling them the same thing is pretty confusing :-). The Unix version also has the nice property that it can actually go above 1; yours doesn't distinguish between a service whose load is at exactly 100% of capacity and barely keeping up, versus one that's at 200% of capacity and melting down. But for load shedding maybe you always want your tripwire to be below that anyway.
Well, I partially disagree with this. The load definition has its equivalent in computing with other metrics that have a close range, such as the CPU one. I've never had the intention to align the load of the loop with the load average, I've just used the concept as an example of the metric that might be used to check how loaded is your system.
More broadly we might ask what's the best possible metric for this purpose – how do we judge? A nice thing about the JavaScript library you mention is that scheduling delay is a real thing that directly impacts the quality of service – it's more of an "end to end" measure in a sense. Of course, if you really want an end to end measure you can do things like instrument your actual logic, see how fast you're replying to HTTP requests or whatever, which is even more valid but creates complications because some requests are supposed to take longer than others, etc. I don't know which design goals are important for real operations.
Here the key for me, something where I should have based my rationale. How good is the way presented to measure a load of your asynchronous system compared with the toobusy one? what can we achieve with this metric?
I will work on that as the base of my rationale for the change proposed. Then, once if the rationale is accepted the implementation is peanuts :)
-- --pau
-- --pau _______________________________________________ Async-sig mailing list Async-sig@python.org https://mail.python.org/mailman/listinfo/async-sig Code of Conduct: https://www.python.org/psf/codeofconduct/
![](https://secure.gravatar.com/avatar/97c543aca1ac7bbcfb5279d0300c8330.jpg?s=120&d=mm&r=g)
On Mon, Aug 21, 2017 at 9:15 AM, Yury Selivanov <yselivanov@gmail.com> wrote:
Hi Pau,
I personally don't think we need this in asyncio. While the function has a relatively low overhead, it's still an overhead, it's a couple more syscalls on each loop iteration, and it's a bit of a technical debt.
The bar for adding new functionality to asyncio is very high, and I don't see a good rationale for adding this function. Is it for debugging purposes? Or for profiling live applications? If it's the latter, then there are so many other things we want to see, and some of them are protocol-specific.
One approach would be to add a generic instrumentation API. Trio has something like this, that I think is expressive enough to let Pau implement their busyness checking as a library: https://trio.readthedocs.io/en/latest/reference-core.html#instrument-api This has several advantages over subclassing: multiple libraries can define their own instrumentation without interfering with each other, you don't have to redefine the instrumentation for every loop implementation, and you don't have to hook up the instrumentation when setting up the loop, e.g. you could just do something like: import toobusy with toobusy.Monitor(loop) as monitor: if monitor.toobusy(): ... -n -- Nathaniel J. Smith -- https://vorpus.org
![](https://secure.gravatar.com/avatar/cf278a5382dde75bd7391751754e80b3.jpg?s=120&d=mm&r=g)
One approach would be to add a generic instrumentation API. Trio has something like this, that I think is expressive enough to let Pau implement their busyness checking as a library:
https://trio.readthedocs.io/en/latest/reference-core.html#instrument-api
This has several advantages over subclassing: multiple libraries can define their own instrumentation without interfering with each other, you don't have to redefine the instrumentation for every loop implementation, and you don't have to hook up the instrumentation when setting up the loop, e.g. you could just do something like:
import toobusy with toobusy.Monitor(loop) as monitor: if monitor.toobusy(): ...
It will help also other loops to meet the same contract making them compatibles with already implemented instruments. Maybe the major concern here is the performance penalty, do you have some numbers about how negligible is have all of these signals available to be used? -- --pau
![](https://secure.gravatar.com/avatar/cf278a5382dde75bd7391751754e80b3.jpg?s=120&d=mm&r=g)
I had an idea based on the Trios implementation. Trying to move the load method implementation out of the loop scope but avoiding the subclassing. My primary concern with the instrumentation was the performance impact. I've run some tests in one experimental branch, instrumenting 4 events for the _run_once function. The idea was to see how the performance degrades, despite there were not listeners/instruments attached. My gut feelings said that only the call to emit the event and the proper check to see if there were listeners will have an expensive performance cost. The numbers were pretty clear, the default Asyncio loses a 10% of performance. Degrading from the 30K coroutines per second to 27K in my laptop. Therefore, looks like we are facing a stopper that would need to be addressed first. How might Python address this situation? Basically implementing a new object type that will provide the same interface as a Signal. Many languages have their own implementation C# [1], Java [2] or they have libraries that support Signals or Events. Indeed Python has plenty of them implemented as independent modules, the most known are Django Signals [3] or Blinker [4]
From my understanding here the problem is the language by itself, having the interpreter behind the scenes.
What I would propose here is a new Python object called Signal implemented as a new type in the CPython API that will allow the developers to implement the following snippet having the chance to speed up the code or at least don't be penalized when there are no observers listening from a specific event. class Foo: def __init__(): self._cb = [] def add_listener(self, cb): self._cp.append(cb) def _signal(self, msg): for cb in self._cb: cb(msg) def run(): self._signal("start") self._signal("stop") f = Foo() f.run() def print_signal(msg): print(msg) f.add_listener(print_signal) f.run() This previous snippet might be implemented using a new object type called signal, having the next snippet: class Foo: def __init__(): self.start = signal() self.stop = signal() def run(): self.start.send() self.stop.send() f = Foo() f.run() def start(): print(msg) def stop(): print(msg) f.start.connect(start) f.stop.connect(stop) f.run() This kind of pattern is spread in many frameworks [5], and they might get benefited for free thanks to this new type of object. I'm wondering if there were other attempts in the past to implement this kind of object in the CPython API. There is some resistance to modify or add superfluous code in the code if it can be implemented as a Python module? The simple fact to become a must for an internal feature might enough to think on that? Thoughts ? [1] http://docs.oracle.com/javase/tutorial/uiswing/events/index.html [2] https://msdn.microsoft.com/en-us/library/aa645739(v=vs.71).aspx [3] https://docs.djangoproject.com/en/1.11/topics/signals/ [4] http://pythonhosted.org/blinker/ [5] http://flask.pocoo.org/docs/0.12/signals/ On Tue, Aug 22, 2017 at 2:36 PM, Pau Freixes <pfreixes@gmail.com> wrote:
One approach would be to add a generic instrumentation API. Trio has something like this, that I think is expressive enough to let Pau implement their busyness checking as a library:
https://trio.readthedocs.io/en/latest/reference-core.html#instrument-api
This has several advantages over subclassing: multiple libraries can define their own instrumentation without interfering with each other, you don't have to redefine the instrumentation for every loop implementation, and you don't have to hook up the instrumentation when setting up the loop, e.g. you could just do something like:
import toobusy with toobusy.Monitor(loop) as monitor: if monitor.toobusy(): ...
It will help also other loops to meet the same contract making them compatibles with already implemented instruments. Maybe the major concern here is the performance penalty, do you have some numbers about how negligible is have all of these signals available to be used? -- --pau
-- --pau
![](https://secure.gravatar.com/avatar/cf278a5382dde75bd7391751754e80b3.jpg?s=120&d=mm&r=g)
Hi,
I personally don't think we need this in asyncio. While the function has a relatively low overhead, it's still an overhead, it's a couple more syscalls on each loop iteration, and it's a bit of a technical debt.
The bar for adding new functionality to asyncio is very high, and I don't see a good rationale for adding this function. Is it for debugging purposes? Or for profiling live applications? If it's the latter, then there are so many other things we want to see, and some of them are protocol-specific.
Let's try to evolve the rationale and put some extra links. The load of an Asyncio loop can be at some point easily inferred using the sleeping time vs the overall time, this information brings us to understand how to saturate is the loop with a metric that informs you how many CPU resources are being used, or most important how many CPU resources left. How helpful can be this method? In our organization, e use back-pressure at the application layer of our REST microservices architecture. It allows us to prevent overloading the services. Once the back pressures kicks in we can scale horizontally our services to cope the current load. This is already implemented for other languages and we are currently working on how to implement it with the aiohttp(asyncio) stack. For more info about this technique these articles [1] [2] We are not the first ones running microservices at scale, and this pattern has been implemented by other organizations. I would like to mention the Google case [2]. From that link I would like to bold the following paragraph: """ A better solution is to measure capacity directly in available resources. For example, you may have a total of 500 CPU cores and 1 TB of memory reserved for a given service in a given datacenter. Naturally, it works much better to use those numbers directly to model a datacenter's capacity. We often speak about the cost of a request to refer to a normalized measure of how much CPU time it has consumed (over different CPU architectures, with consideration of performance differences). In a majority of cases (although certainly not in all), we've found that simply using CPU consumption as the signal for provisioning works well """
From my understanding, the comment is pretty aligned with the implementation proposal for the Asyncio loop Having, as a result, a way to measure if there are enough resources to cope the ongoing metric.
[1] https://dzone.com/articles/applying-back-pressure-when [2] http://engineering.voxer.com/2013/09/16/backpressure-in-nodejs/ [3] https://landing.google.com/sre/book/chapters/handling-overload.html
If we want to add some tracing/profiling functions there should be a way to disable them, otherwise the performance of event loops like uvloop will degrade, and I'm not sure that all of its users want to pay a price for something they won't ever be using. All of this just adds to the complexity.
The goal will be, have an implementation without impact performance for real applications. I'm still not sure if this is reachable with the uvloop, I would like to start working on this as soon as possible, having the proper numbers and the possibilities to implement this in libuv will help to get the proper answer. If at last, there is no way to make it negligible, then I would agree that is needed a way to switch off or switch on.
participants (4)
-
Chris Jerdonek
-
Nathaniel Smith
-
Pau Freixes
-
Yury Selivanov