-
Notifications
You must be signed in to change notification settings - Fork 3k
bd: Add ProfilingBlockDevice for measuring higher-level applications #4844
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
Conversation
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 believe counter should only be incremented if the operation they monitor succeed.
I also wonder if it is necessary to make reset
, get_read_count
, get_write_count
and get_erase_count
virtual.
|
||
int ProfilingBlockDevice::read(void *b, bd_addr_t addr, bd_size_t size) | ||
{ | ||
_read_count += size; |
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.
Increment of read count should be made only if the read operation was a success.
|
||
int ProfilingBlockDevice::program(const void *b, bd_addr_t addr, bd_size_t size) | ||
{ | ||
_program_count += size; |
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.
Increment of the program count should be made only if the program operation was a success.
|
||
int ProfilingBlockDevice::erase(bd_addr_t addr, bd_size_t size) | ||
{ | ||
_erase_count += size; |
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.
Increment of the erase count should be made only if the erase operation was a success.
Ah good point! I'll update
I mean we already have a vtable ¯_(ツ)_/¯. I'll go ahead and make them non-virtual, I don't really have a preference. |
@geky Thanks for the PR. Please change the PR subject line to "Add ProfilingBlockDevice to measure high-level applications" for clarity and brevity because we use our merged PR titles in our release notes. |
What is difference to the current one - to measure vs for measuring plus the prefix? I like there the prefix bd: (the module that this applies to). |
Those functions are getter to data member, I cannot think of any use case where overriding is necessary or even wanted. But if a developer find a use case and derive the class, it would have to understand the relationship (and state modification) My advice would be to makes data member private and remove the virtual specifier out of the |
@0xc0170 We recommend our contributors follow Chris Beam’s seven rules of great commit messages. The current subject line breaks two of those rules [length and capitalization of the first letter]. Because "ProfilingBlockDevice" has "BlockDevice" in it, the prefix isn't necessary. It's not a big deal, but it's nice to have consistency in the release notes. |
@pan-, isn't that where calling the superclass's member functions by name becomes useful? (ie Here's an interesting counter example, consider TimingProfilingBlockDevice, a block device that also measure the time operations take. We could extend the ProfilingBlockDevice to make use of it's existing function and would probably want to reuse reset: class TimingProfilingBlockDevice : public ProfilingBlockDevice {
// blablablabla
virtual timestamp_t get_runtime() const {
return _time;
}
virtual reset() {
_time = 0; // reset our timing
ProfilingBlockDevice::reset(); // also reset parent
}
} Though maybe this is only a concern for reset... @AnotherButler, actually, reading through Chris Beam's rules, he doesn't really cover repositories with multiple modules like mbed OS. In the comments this same question came up, and it seems like the consensus is to use "module: Some title": Keeping a consistent module name is very useful for greping for related commits. (We don't have an enforced policy so it's not thaaat useful, but would be nice to have ;) ) |
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.
maybe there could be also counter for failure cases where e.g. programming fails..
@geky @jupe Even if the class is extended to provide new kind of profiling features the data member The design of the Ideally a It would also be possible to compose these all profiling utility types together in a non intrusive manner. One solution would be to use a decorator. Similar to what has been done with the Another solution would be to decouple the instrumentation from the struct BlockDeviceInstrument {
virtual ~BlockDeviceInstrument() { }
virtual void before_read(void *buffer, bd_addr_t addr, bd_size_t size) { }
virtual void after_read(int result, void *buffer, bd_addr_t addr, bd_size_t size) { }
virtual void before_program(const void *buffer, bd_addr_t addr, bd_size_t size) { }
virtual void after_program(int result, const void *buffer, bd_addr_t addr, bd_size_t size) { }
virtual void before_erase(bd_addr_t addr, bd_size_t size) { }
virtual void after_erase(int result, bd_addr_t addr, bd_size_t size) { }
virtual void reset() { }
};
class InstrumentedBlockDevice : public BlockDevice {
public:
InstrumentedBlockDevice(BlockDevice* device, BlockDeviceInstrument& instrument) :
_block_device(device), _instrument(instrument) { }
virtual int read(void *buffer, bd_addr_t addr, bd_size_t size) {
_instrument.before_read(buffer, addr, size);
int result = _block_device->read(buffer, addr, size);
_instrument.after_read(result, buffer, addr, size);
return result;
}
virtual int program(const void *buffer, bd_addr_t addr, bd_size_t size) {
_instrument.before_program(buffer, addr, size);
int result = _block_device->program(buffer, addr, size);
_instrument.after_program(result, buffer, addr, size);
return result;
}
virtual int erase(bd_addr_t addr, bd_size_t size) {
_instrument.before_erase(addr, size);
int result = _block_device->erase(addr, size);
_instrument.after_erase(result, addr, size);
return result;
}
private:
BlockDevice* _block_device;
BlockDeviceInstrument& _instrument;
}; It become possible to create any kind of new instrument and use it in the instrumented block device class: // generic counter
class BlockDeviceInstrumentCounter : public BlockDeviceInstrument {
public:
BlockDeviceInstrumentCounter() : counter(0) { }
virtual void reset() {
counter = 0;
}
size_t get_counter() const {
return counter;
}
protected:
size_t counter;
};
// instrument successful read
class BlockDeviceReadCounter : public BlockDeviceInstrumentCounter {
public:
virtual void after_read(int result, void *buffer, bd_addr_t addr, bd_size_t size) {
if (result == 0) {
counter += size;
}
}
};
// instrument failure
class BlockDeviceReadFaillureCounter : public BlockDeviceInstrumentCounter {
public:
virtual void after_read(int result, void *buffer, bd_addr_t addr, bd_size_t size) {
if (result != 0) {
++counter;
}
}
}; Switching from one type of instrumentation to another really just is changing the instrument injected in the void instrument_faillure(BlockDevice* block_device) {
BlockDeviceReadFaillureCounter faillure_counter;
InstrumentedBlockDevice bd(block_device, faillure_counter);
// do some work
size_t faillures = faillure_counter.get_counter();
}
void instrument_read(BlockDevice* block_device) {
BlockDeviceReadCounter read_counter;
InstrumentedBlockDevice bd(block_device, read_counter);
// do some work
size_t bytes_reads = read_counter.get_counter();
} Composition of instruments can be handled by a composite of Instrument object: struct CompositeBlockDeviceInstrument : BlockDeviceInstrument {
template<size_t N>
CompositeBlockDeviceInstrument(BlockDeviceInstrument* (&instruments)[N]) :
_instruments(instruments), _count(N) { }
virtual void before_read(void *buffer, bd_addr_t addr, bd_size_t size) {
for (size_t i = 0; i < _count; ++i) {
_instruments[i]->before_read(buffer, addr, size);
}
}
virtual void after_read(int result, void *buffer, bd_addr_t addr, bd_size_t size) {
for (size_t i = 0; i < _count; ++i) {
_instruments[i]->after_read(result, buffer, addr, size);
}
}
virtual void before_program(const void *buffer, bd_addr_t addr, bd_size_t size) {
for (size_t i = 0; i < _count; ++i) {
_instruments[i]->before_program(buffer, addr, size);
}
}
virtual void after_program(int result, const void *buffer, bd_addr_t addr, bd_size_t size) {
for (size_t i = 0; i < _count; ++i) {
_instruments[i]->after_program(result, buffer, addr, size);
}
}
virtual void before_erase(bd_addr_t addr, bd_size_t size) {
for (size_t i = 0; i < _count; ++i) {
_instruments[i]->before_erase(addr, size);
}
}
virtual void after_erase(int result, bd_addr_t addr, bd_size_t size) {
for (size_t i = 0; i < _count; ++i) {
_instruments[i]->after_erase(result, addr, size);
}
}
virtual void reset() {
for (size_t i = 0; i < _count; ++i) {
_instruments[i]->reset();
}
}
private:
BlockDeviceInstrument** _instruments;
size_t _count;
}; Composition is easy, cheap and extensible. Since there is no long inheritance chain, name clash are also avoided: void instrument_read_and_faillure(BlockDevice* block_device) {
BlockDeviceReadFaillureCounter faillure_counter;
BlockDeviceReadCounter read_counter;
BlockDeviceInstrument* instruments_list[] = {
&read_counter, &faillure_counter
};
CompositeBlockDeviceInstrument instruments(instruments_list);
InstrumentedBlockDevice bd(block_device, instruments);
// do some work
size_t faillures = faillure_counter.get_counter();
size_t bytes_read = read_counter.get_counter();
} But what has been gained from architecturing the code:
Note that it is also possible to avoid virtual functions and use static polymorphism: https://gist.github.com/pan-/b136a3d56200c0f1647d43d1315ed427 . |
@geky In general*, I don't have a problem with using a colon after a module as a prefix. However, in this case, it adds extra letters to a subject line that's already longer than the recommended length, and those letters don't add any value because we already know we're talking about BlockDevice. *Today's release note has a few entries with multiple colons, which I do not like because it gets confusing. |
Would |
Actually I think we do have a policy: https://github.com/ARMmbed/mbed-os-morpheus/blob/guidelines/GUIDELINES.md#contribution . |
@jupe, that's a good idea, but write errors usually aren't detected at this level. It could be added later if we find it useful. @pan-, that's neat 👍, though might be a bit overkill for this class in the little corner of the bd api. It's probably simpler to just shove any other metrics we want into this single class for now. @AnotherButler, no tweaking is going to make that commit message fit in 50 characters aside from dropping "ProfilingBlockDevice", but as that's the class name and seems a bit important. The point of a consistent module name is to be consistent, and "bd" is already somewhat useful. Also it's only 3 letters, I could make the name even shorter by removing all of the whitespace. @theotherjimmy, that is useful. But doesn't work when filtering commits in releases or github issues/prs. @pan-, that's why I tossed in the word "enforced" ; ) . I'm sure we'll get there eventually. |
*/ | ||
bd_size_t get_erase_count() const; | ||
|
||
protected: |
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.
Could you make these variable private ?
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.
Oh! I'm not sure how I didn't notice that, sure thing
@geky I know its overkill, especially if it is not needed right now however I wanted to stress out that virtuality and protected access by default, just in case, open the door to poor design. It is not a small decision to make a class part of the public API because it has to be maintained and supported once it is public. Design patterns, best practices or programming principles (KISS, YAGNI, SOLID, ...) are not just fancy concepts but rather are valuable tools. A legacy of thousands of failures and success which can help us in delivering better software if applied with a pragmatic thinking. |
The member variables were never intended to be protected, I'm actually not sure how they got there in the first place. Virtualness is a bit different. What concerns are there besides the runtime/memory cost? |
First virtualness indicates that the function can be overridden and a feature of a class can be extended. This is not desirable for every function or for every class. Incorrect overloading; it is so easy to get it wrong. For example if one of the accessor is overloaded or a new one is added then the The virtual specifier also indicates that derivation is the means chosen to extend class features. Is it the best way to add new profiling feature ? |
Ok, the point about mbed stating a preference for extending makes sense to me. Does this pr look good as is? |
@geky Yes it looks good 👍 . |
Cool beans : ) /morph test-nightly |
Result: FAILUREYour command has finished executing! Here's what you wrote!
OutputTest failed! |
profiling_of_block_devices tests seems to failing for all platform
|
Woops! nice catch |
Result: FAILUREYour command has finished executing! Here's what you wrote!
OutputTest failed! |
/morph test-nightly |
Result: FAILUREYour command has finished executing! Here's what you wrote!
OutputTest failed! |
/morph test-nightly |
Result: SUCCESSYour command has finished executing! Here's what you wrote!
OutputAll builds and test passed! |
mbed OS 5.5.6 release We are pleased to announce the [mbed OS 5.5.6 release](https://github.com/ARMmbed/mbed-os/releases/tag/mbed-os-5.5.6) is now available. This release includes ... Known Issues The following list of known issues apply to this release: Contents Ports for Upcoming Targets [4608](ARMmbed#4608) Support Nuvoton's new target NUMAKER_PFM_M487 [4840](ARMmbed#4840) Add Support for TOSHIBA TMPM066 board Fixes and Changes [4801](ARMmbed#4801) STM32 CAN: Fix issue with speed function calculation [4808](ARMmbed#4808) Make HAL & US tickers idle safe [4812](ARMmbed#4812) Use DSPI SDK driver API's in SPI HAL driver [4832](ARMmbed#4832) NUC472/M453: Fix several startup and hal bugs [4842](ARMmbed#4842) Add call to DAC_Enable as this is no longer done as part [4849](ARMmbed#4849) Allow using of malloc() for reserving the Nanostack's heap. [4850](ARMmbed#4850) Add list of defines to vscode exporter [4863](ARMmbed#4863) Optimize memory usage of wifi scan for REALTEK_RTW8195AM [4869](ARMmbed#4869) HAL LPCs SPI: Fix mask bits for SPI clock rate [4873](ARMmbed#4873) Fix Cortex-A cache file [4878](ARMmbed#4878) STM32 : Separate internal ADC channels with new pinmap [4392](ARMmbed#4392) Enhance memap, and configure depth level [4895](ARMmbed#4895) Turn on doxygen for DEVICE_* features [4817](ARMmbed#4817) Move RTX error handlers into RTX handler file [4902](ARMmbed#4902) Using CMSIS/RTX Exclusive access macro [4923](ARMmbed#4923) fix export static_files to zip [4844](ARMmbed#4844) bd: Add ProfilingBlockDevice for measuring higher-level applications [4896](ARMmbed#4896) target BLUEPILL_F103C8 compile fix [4921](ARMmbed#4921) Update gcc-arm-embedded PPA in Travis [4926](ARMmbed#4926) STM32L053x8: Refactor NUCLEO_L053R8 and DISCO_L053C8 targets [4831](ARMmbed#4831) Remove excessive use of printf/scanf in mbed_fdopen/_open [4922](ARMmbed#4922) bug fix: xdot clock config [4935](ARMmbed#4935) STM32: fix F410RB vectors size [4940](ARMmbed#4940) Update mbed-coap to version 4.0.9 [4941](ARMmbed#4941) Update of MemoryPool.h header file. You can fetch this release from the [mbed-os GitHub](https://github.com/ARMmbed/mbed-os) repository, using the tag "mbed-os-5.5.6". Please feel free to ask any questions or provide feedback on this release [on the forum](https://forums.mbed.com/), or to contact us at [[email protected]](mailto:[email protected]).
This utility block device just has internal counters for keeping track of the number of bytes read/programmed/erased. Required for filesystem benchmarking.