-
-
Notifications
You must be signed in to change notification settings - Fork 36
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 ICacheMonitor to allow monitoring extensions to be added #180
base: main
Are you sure you want to change the base?
Conversation
Firstly, this is pretty awesome! I've had a quick look now and there are a lot of things I like and a few things to consider. The biggest thing for me - it would be great if it was part of the extension system. My main reasoning for this is it keeps the handling of all the functionality that extends Cache Tower in one spot - this is similar to how I plan to add logging later too. The second part relates to allocations with the timer - it would be interesting to see if we could either pool the timer or work out a way to have a low-allocation variant of it. Having it be on the hot path of a number of these methods could add GC pressure we might want to avoid. This is a problem to handle more down the line when it comes to benchmarking etc. I'll have a second look at it tomorrow to see if I have any other thoughts but overall, I'm super appreciative of the effort you've put into doing this! |
I was pondering the extensions and using them instead after you spoke about them on the linked issue - However, I see that they are evaluated after the actions are carried out. This means that you would be storing this timer/result within the calling function, and then needing to pass it down into the extension chain. I tried to think of a clean way of accomplishing that, and I failed on a first pass. Furthermore, it is very useful to know about the hit/miss events per layer - the extensions run after all this has been evaluated, meaning this information isn't easy to convey. You would need to build up a whole "record" of layer/timing/result information, then pass this payload down into a special extension hook. That also adds to allocations as it would be generating lots of small objects. I felt this is was sufficiently high level that it could slot into the hot paths. My main worry of it currently is the timer allocation as you have also mentioned. With regards to a pooled timer - I was concerned about multithreading cases - we need to isolate the timer for a caller. However, I wonder if we can just use system ticks and fetch those before and after the calls, and derive the timing information from that. To be honest though - without benchmarking it is all guess work, so I guess we need to make a couple of versions and see what the tradeoffs are on each. I figured that testing if CacheMonitor == null -> just use the old paths, otherwise if CacheMonitor != null -> allocate timer, and use new paths. Although making more branching logic, this would be faster than the NoOpCacheMonitor approach and give less GC pressure. As a consumer of the library I would take a performance hit in order to have detailed statistics - but if I needed raw performance, I would also have the option to turn it off. |
All that being said, I would be very interested to see what you had in mind for the extensions - I feel like I might have missed something obvious, but I couldn't get my head around a way that make its nice and generic and open enough. This doesn't feel generic, it feels very specific to me. Appreciate the feedback! |
I reckon we should be able to achieve both - detailed statistics and blazing fast performance. I'll think about what you've said re extensions, you're likely right but I'm still curious if there is still a neat way to use it without it being too complicated. Will get back to you tomorrow! |
So it took a little longer to get back to you but here is my thoughts... I think it could still be as an extension but it requires a core rewrite of the extension system. Extensions currently work through fixed interfaces that are directly called through the extension container. Instead, most of the extensions could be done through proper C# events and then those could be called directly, I know that doesn't really seem super relevant here but the idea then could be proper events at the points that make sense for metrics. With that in mind though, here is some more specific feedback to what you've suggested...
For example: var timer = TimingUtils.StartTiming();
// do work
eventName?.Invoke(this, new MyCustomEventArgs(foo, bar, timer.Elapsed); The Also I had an idea how to get a high precision non-allocating timer. I found out that In the end, all of this would mean that a CacheStack (potentially Does that make any sense? I'm writing this late at night and am not sure. |
Hey, Sorry, delay on my end as well - I have been pondering it the last few days though:
Yup, that makes sense
Ah, good point. In fact, going on from your point later, we can have a "global" Stopwatch running, and our
The issue with events is that the data flow is one way only - they fire and forget, and can only allow data flow out. This will not be compatible with your existing My initial feelings around events was thinking its a bit dirty on the API surface (they aren't nicely wrapped up into a component which can be replaced in CacheStack), but the more I have thought about it, the more it makes sense from a speed and performance perspective. Just have to be careful to not end up with loads of (almost duplicate) events because different extensions require different data payloads. I think for the existing setup however, most extensions (e.g. When an extension is hooked into CacheStack, I guess a dispose pattern could hide away the complexity of binding/unbinding events: public class CacheStackMonitorExtension : IDisposable
{
private readonly CacheStack _cacheStack;
public CacheStackMonitorExtension(CacheStack cacheStack)
{
_cacheStack = cacheStack;
_cacheStack.SomeEvent += LocalHandler;
}
public Dispose()
{
_cacheStack.SomeEvent -= LocalHandler;
}
} This could be managed by a IoC container along with the CacheStack. |
Yeah, there would still likely need to be extensions closer to the same pattern I've already got for things like the
Yeah, absolutely. I want to make sure I've thought ahead enough to avoid large breaking changes while also giving flexibility to grow. Even having too many events could end up hurting performance unnecessarily.
I wonder how necessary it would be to need to remove the events later. Currently extensions are applied to a |
Hi @Turnerj
I had some time so I thought I would have a quick sketch out of what I was thinking the other day regarding #157
I haven't added tests/documentation etc. as it was more for a discussion first, rather than a PR being ready for merge.
Off the top of my head, this would meet the requirements for monitoring layer/key/performance and leaving it up to the consumer to aggregate the event/timing information as they see fit.
Open for discussion, feedback, whatever really. Benchmarks should be performed as well to make sure this isn't hampering performance. I guess the timing doesn't need to happen if no CacheMonitor - so maybe it is better to branch each operation instead of using
NoOpCacheMonitor
and only instantiate timers if monitoring is enabled.I pushed the layer name to be exposed via ICacheLayer to avoid reflection being needed to resolve the layer name on each call.
Food for thought anyway.