Creating a Simple Device Library for Intel® Energy Checker SDK

For my first blog entry on the Intel® Energy Checker SDK, I will show how to write an ESRV support library which will simulate a power draw slightly more sophisticated than the stock esrv_simulated_device library of the SDK. To give a bit of context, remember that ESRV is the SDK tool in charge of driving a power analyzer and sampling the power and energy readings. This data is then made available to software (SW) using the SDK API. One of the key benefits of processing this way is to completely decouple SW from the burden of measuring power. Also, if you change the power reading device; you will not have to change your freshly instrumented code. Note that in this blog entry, I will not make use of the SDK API.

Why a new simulated device library? The reason is simple: the stock library as the documentation explains it generates a random power reading using the following C code: 150.0 + (rand() % 10);. As you can see, nothing fancy here. My objective for this blog entry is to write an ESRV library which will generate a power reading indexed upon the processor utilization of the system. For example, let’s plan to use the following formula: 1.5 * cpu_utilization (with cpu_utilization being a number between 0 and 100%). Since we are not using the SDK API in this blog entry, the purpose of this exercise is to provide an extra sample to device manufacturers on what needs to be done to interface their power reader with ESRV. It will also provide you a bit more realistic simulated device – and a good idea for a fun project (or at least I hope so:)).

Where to start?

The SDK is shipped with a set of template device library code. I will use the source and header files located in the \iecsdk\utils\device_driver_kit\src\energy_meter_driver folder as a starting point. The new library files are referenced at the end of the blog entry, so you will not have to modify these reference files if you just want to build and use the new library.

As the Device Driver Kit User Guide specifies, there are a set of mandatory and optional functions a device manufacturer must/can implement in their support library. In this sample, we will only implement the mandatory functions for the sake of brevity. These functions are: init_device_extra_data(), delete_device_extra_data(), open_device(), close_device(), parse_device_option_string(), read_device_power() and read_device_energy(). Because our library is very simple, delete_device_extra_data(), parse_device_option_string() and read_device_energy() are empty functions always returning ESRV_SUCCESS. The rationales are respectively: no device data requires dynamic allocation/de-allocation, no options are available to the user and the simulated device will rely on ESRV to perform the power integration to compute energy.

Tell ESRV what we can do

Based on these light weight specifications, we can start crafting the init_device_extra_data() function. This function is called by ESRV once the library was successfully loaded. It is the right place to indicate the device’s capabilities. In our case, we want to express that:

    • Our device uses a proprietary interface. Indeed, we do not use a serial or a network interface since we are just simulating a power draw. This way, ESRV will not try to drive the interface for us.

    • Our device does not have hardware power integration capability. This could be added later. However, devices having such capability usually provide a more accurate energy reading since they can oversample the power in comparison to the 1Hz default sampling rate of ESRV.

    • Our device has only one virtual device or channel. This simply means that our library simulates a single power reading.

The code below reproduces the init_device_extra_data() function. Lines in bold reflect the described settings. Note that I’ve implemented the processor usage indexing functionality only for the Windows operating systems. If the code is compiled under a different OS, then it will behave just as the stock library. However, the code has all preprocessor directives in place so you can implement the indexing feature under Linux, Solaris 10, or MacOS X.

ESRV_API int init_device_extra_data(PESRV p) {
static DEVICE_DATA data;
static int f_first_init_call = 0;
if(!p) { goto init_device_extra_data_error; }
if(f_first_init_call == 0) {
memset(&data, 0, sizeof(DEVICE_DATA));
px = (void *)&data;
#ifdef __PL_WINDOWS__
_tcsncpy(px->cpu_counter, CPU_COUNTER, _tcslen(CPU_COUNTER));
#endif // __PL_WINDOWS__
#if defined (__PL_LINUX__) || (__PL_SOLARIS__) || (__PL_MACOSX__)
px->dummy = 0; // replace with real code for other OSes
#endif // __PL_LINUX__ || __PL_SOLARIS__ || __PL_MACOSX__
p->device_data.virtual_devices = 1;
p->f_hw_energy_integration_provided = 0;
p->device_data.p_device_data = (void *)&data;
f_first_init_call = 1;
} else {

Based upon user provided options and device capabilities, ESRV next calls parse_device_option_string followed by a second call to init_device_extra_data (calls are differentiated using the static f_first_init_call flag). By proceeding this way, ESRV allows device manufacturers to create and set device specific data needed to serve the user’s request. Indeed, ESRV has an option --device_options . The string provided by the user via this option is passed to the support library when parse_device_option_string is called. For example, this is the mechanism we use to pass the serial number to the Yokogawa WT500 power analyzer to its support library. This data is required to communicate with the device. In our case, we do not offer any options, but this sample could be very simply enhanced through this mechanism. By offering no options, the second call to init_device_extra_data() and the call to parse_device_option_string() do nothing and always return successfully.

We are almost there!

What happens next is fairly straight forward. ESRV calls the open_device function. Symmetrically, at the end of ESRV’s run the close_device function is called. In between, the read_device_power function is called every second by ESRV. Let’s start with the open_device function. Since we are simply simulating a device, we won’t have to perform the usual tasks a device manufacturer needs to do (initialize the device, configure the device, start the measurement, etc.). However, we need to setup the mechanism required to collect the processor usage. Under Windows, I am relying upon the PDH library (pdh.lib). Please refer to Microsoft’s documentation for more details. In short, we need to create a query, and add a counter to that query before we can read the processor utilization as seen and reported by the OS. The counter I use is "\\Processor(_Total)\\% Processor Time". An interesting variation could be to use either a different or additional counters to simulate an odd or a more realistic power draw scheme. For example, by adding to the equation the amount of memory used, one could enrich the simple model we are implementing here. The code snippet extracted from the open_device function shows these steps. The close_device function simply cleans-up the query.

#ifdef __PL_WINDOWS__
// open query
status = PdhOpenQuery(NULL, 0, &pd->hquery);
if(status != ERROR_SUCCESS) { goto open_device_error; }

// add cpu counter to the query
status = PdhAddCounter(pd->hquery, pd->cpu_counter, 0, &pd->handle);
if(status != ERROR_SUCCESS) { goto open_device_error; }
#endif // __PL_WINDOWS__

Power, power, power…

The last piece of code to write is the device_read_power function. Each time it is called by ESRV, we need to measure the processor utilization and then generate a simulated power draw proportional to this value. As we decided earlier, we will scale the arbitrary 150 Watts of the stock library by the processor usage percentage. The most complicated part is to read the processor usage. We do so by collecting data for the query we have created in the open_device function. The code snippet form the device_read_power function shows these steps.

#ifdef __PL_WINDOWS__
// read CPU utilization from the query
status = PdhCollectQueryData(pd->hquery);
if(status != ERROR_SUCCESS) { goto read_device_power_error; }

// interprete data
status = PdhGetFormattedCounterValue(pd->handle, PDH_FMT_DOUBLE, NULL, &pd->counter_value);
if(status != ERROR_SUCCESS) { goto read_device_power_error; }
p->double_power = 1.5 * pd->counter_value.doubleValue;
#endif // __PL_WINDOWS__

What next?

At this stage, simply build the library and you are good to go (please read the top of the source file to know which symbols needs to be defined). This new functionality will allow you to see a direct impact of the processor utilization on the energy efficiency data you are computing within your application!
As indicated earlier, it is very simple to add similar functionality under Linux, Solaris 10 and MacOS X. For Linux, you can peek into the linux_cpu_and_loadavg_info.c file located in \iecsdk\src\samples\linux_system_infos. An interesting extra step could be for you to add extra system counters into the dummy power reading calculation. At the end of the day, you could proof your power draw model against the real power reading of one of your systems! I actually may want to do that…if I ever have some spare time :).
Last but not least, I also hope that this extra sample will be of some help to device manufacturers willing to provide an ESRV support library for their devices.

In my first blog entry for the Intel® Energy Checker SDK, I wanted to break the ice. Hope I did it. Before ending, I want to say that Kevin and I are really excited by the research ideas, algorithm enhancements and energy saving heuristics that you, the SW developers, will invent in the future. Indeed, I am deeply convinced that the SW is the component best able to project itself into the future and make substantial energy savings, where the hardware and the OS, however smart they may be, are stuck in the past. Let’s put the SW into the energy savings loop where it belongs! And, if the Intel® Energy Checker SDK can help us to achieve energy aware and energy efficient software, then I will be delighted.

Jamel Tayeb


For more complete information about compiler optimizations, see our Optimization Notice.