Skip to content

Commit

Permalink
Add MeasurementService demo service (LLNL#309)
Browse files Browse the repository at this point in the history
* Add MeasurementService demo service

* codecov: Ignore build/
  • Loading branch information
daboehme authored Oct 8, 2020
1 parent 27af6ac commit 0071b31
Show file tree
Hide file tree
Showing 11 changed files with 369 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,10 @@ else()
set(CALIPER_REDUCED_CONSTEXPR_USAGE FALSE)
endif()

if (BUILD_TESTING)
set(CALIPER_BUILD_TESTING TRUE)
endif()

# Create a config header file
configure_file(
${PROJECT_SOURCE_DIR}/caliper-config.h.in
Expand Down
2 changes: 2 additions & 0 deletions caliper-config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@

#cmakedefine CALIPER_ENABLE_HISTOGRAMS

#cmakedefine CALIPER_BUILD_TESTING

#ifdef CALIPER_REDUCED_CONSTEXPR_USAGE
#define CONSTEXPR_UNLESS_PGI
#else
Expand Down
1 change: 1 addition & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
coverage:
ignore:
- build/**
- ext/**
- test/**
- **/test/**
Expand Down
4 changes: 4 additions & 0 deletions include/caliper/SnapshotRecord.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ class SnapshotRecord
}
}

void append(const cali::Attribute& attr, const cali::Variant& data) {
append(attr.id(), data);
}

Sizes capacity() const {
return { m_capacity.n_nodes - m_sizes.n_nodes,
m_capacity.n_immediate - m_sizes.n_immediate };
Expand Down
4 changes: 4 additions & 0 deletions src/services/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ if (CALIPER_HAVE_PCP)
add_subdirectory(pcp)
endif()

if (CALIPER_BUILD_TESTING)
add_subdirectory(templates)
endif()

add_service_sources(Services.cpp)

configure_file(
Expand Down
19 changes: 19 additions & 0 deletions src/services/templates/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
set(CALIPER_SERVICE_TEMPLATE_SOURCES
MeasurementService.cpp)

# Use the add_service_sources() macro to add sources without external dependencies.
# For services that do have external dependencies (a third-party library), create
# an object library instead and use the add_service_objectlib macro instead:
#
# include_directories(${<dependency>_INCLUDE_DIRS})
# add_library(caliper-templateservices OBJECT ${CALIPER_SERVICE_TEMPLATE_SOURCES})
#
# add_service_objlib("caliper-templateservices")
add_service_sources(${CALIPER_SERVICE_TEMPLATE_SOURCES})

# Use the add_caliper_service() macro to add the service(s).
# The first word in the string must be the name used in the service's
# CaliperService variable name and name element. The second (optional) word
# is a #ifdef condition in caliper-config.h that must be met for the
# service.
add_caliper_service("measurement_template CALIPER_BUILD_TESTING")
271 changes: 271 additions & 0 deletions src/services/templates/MeasurementService.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
// Copyright (c) 2020, Lawrence Livermore National Security, LLC.
// See top-level LICENSE file for details.
//
// SPDX-License-Identifier: BSD-3-Clause

// This is a template for a measurement service (adding measurements
// to a Caliper snapshot) as developer demo and documentation.

#include "caliper/CaliperService.h"

#include "caliper/Caliper.h"
#include "caliper/SnapshotRecord.h"

#include "caliper/common/RuntimeConfig.h"
#include "caliper/common/Log.h"

#include <chrono>
#include <mutex>
#include <tuple>
#include <vector>

using namespace cali;

namespace cali
{

extern cali::Attribute class_aggregatable_attr;

}

namespace
{

typedef std::chrono::system_clock Clock;
typedef std::chrono::time_point<Clock> TimePoint;

// Our "measurement function"
std::tuple<bool, uint64_t> measure(const TimePoint& start, const std::string& name) {
auto now = Clock::now();
uint64_t val =
name.length() * std::chrono::duration_cast<std::chrono::microseconds>(now - start).count();

return std::make_tuple(true, val);
}

// The MeasurementTemplateService class serves as template/demo/documentation
// for writing Caliper measurement services.
// It reads a list of names from the CALI_MEASUREMENT_TEMPLATE_NAMES config
// variable. For each name, it appends "measurement.val.<name>" and
// "measurement.<name>" entries (absolute value and delta-since-last-snapshot
// for a performance measurement) to Caliper snapshot records.
class MeasurementTemplateService
{
static const ConfigSet::Entry s_configdata[]; // Configuration variables for this service

struct MeasurementInfo {
std::string name; // Measurement name / ID
Attribute value_attr; // Attribute for the measurement value
Attribute delta_attr; // Attribute for the delta value (difference since last snapshot)
Attribute prval_attr; // A hidden attribute to store the previous measurement value on the Caliper blackboard
};

std::vector<MeasurementInfo> m_info; // Data for the configured measurement variables

unsigned m_num_errors; // Number of measurement errors encountered at runtime
TimePoint m_starttime; // Initial value for our measurement function

void snapshot_cb(Caliper* c, Channel* /*channel*/, int /*scopes*/, const SnapshotRecord* /*trigger_info*/, SnapshotRecord* rec) {
// The snapshot callback triggers performance measurements.
// Measurement services should make measurements and add them to the
// provided SnapshotRecord parameter, e.g. using rec->append().

// This callback can be invoked on any thread, and inside signal
// handlers. Make sure it is threadsafe. If needed, use
// c->is_signal() to determine if you are running inside a signal
// handler.

// Make measurements for all configured variables
for (const MeasurementInfo& m : m_info) {
bool success;
uint64_t val;

std::tie(success, val) = measure(m_starttime, m.name);

// Check for measurement errors. Best practice is to count and
// report them at the end rather than printing error messages at
// runtime.
if (!success) {
++m_num_errors;
continue;
}

// Append measurement value to the snapshot record
Variant v_val(cali_make_variant_from_uint(val));
rec->append(m.value_attr, v_val);

// We store the previous measurement value on the Caliper thread
// blackboard so we can compute the difference since the last
// snapshot. Here, c->exchange() stores the current and returns
// the previous value. Compute the difference and append it.
Variant v_prev = c->exchange(m.prval_attr, v_val);
rec->append(m.delta_attr, cali_make_variant_from_uint(val - v_prev.to_uint()));
}
}

void post_init_cb(Caliper* /*c*/, Channel* /*channel*/) {
// This callback is invoked when the channel is fully initialized
// and ready to make measurements. This is a good place to initialize
// measurement values, if needed.

m_starttime = Clock::now();
}

void finish_cb(Caliper* c, Channel* channel) {
// This callback is invoked when the channel is being destroyed.
// This is a good place to shut down underlying measurement libraries
// (but keep in mind multiple channels may be active), report errors,
// and print any debug output. Do NOT use Caliper API calls here, as
// the services they rely on may already be destroyed.

if (m_num_errors > 0)
Log(0).stream() << channel->name() << ": measurement: "
<< m_num_errors << " measurement errors!"
<< std::endl;
}

MeasurementInfo create_measurement_info(Caliper* c, Channel* channel, const std::string& name) {
MeasurementInfo m;

m.name = name;

// Create Caliper attributes for measurement variables, one for the
// absolute value and one for the difference since the last snapshot.
// Do this during service registration. Attributes are the keys for
// Caliper's key:value snapshot records.

// Attributes have a name, datatype, flags, and optional metadata.
// As a convention, we prefix attributes names for services with
// "<service name>." Datatype here is unsigned int. Use the ASVALUE
// flag to store entries directly in snapshot records (as opposed to
// the Caliper context tree). Use SKIP_EVENTS to avoid triggering
// events when using set/begin/end on this attribute. This attribute
// is for absolute measurement values for <name>.
m.value_attr =
c->create_attribute(std::string("measurement.val.") + name,
CALI_TYPE_UINT,
CALI_ATTR_SCOPE_THREAD |
CALI_ATTR_ASVALUE |
CALI_ATTR_SKIP_EVENTS);

Variant v_true(true);

// The delta attribute stores the difference of the measurement
// value since the last snapshot. We add the "class.aggregatable"
// metadata attribute here, which lets Caliper aggregate these values
// automatically.
m.delta_attr =
c->create_attribute(std::string("measurement.") + name,
CALI_TYPE_UINT,
CALI_ATTR_SCOPE_THREAD |
CALI_ATTR_ASVALUE |
CALI_ATTR_SKIP_EVENTS,
1, &class_aggregatable_attr, &v_true);

// We use a hidden attribute to store the previous measurement
// for <name> on Caliper's per-thread blackboard. This is a
// channel-specific attribute, so we encode the channel ID in the
// name.
// In case more thread-specific information must be stored, it is
// better to combine them in a structure and create a CALI_TYPE_PTR
// attribute for this thread info in the service instance.
m.prval_attr =
c->create_attribute(std::string("measurement.pv.") + std::to_string(channel->id()) + name,
CALI_TYPE_UINT,
CALI_ATTR_SCOPE_THREAD |
CALI_ATTR_ASVALUE |
CALI_ATTR_HIDDEN |
CALI_ATTR_SKIP_EVENTS);

return m;
}

MeasurementTemplateService(Caliper* c, Channel* channel)
: m_num_errors(0)
{
// Get the service configuration. This reads the configuration
// variables defined in s_configdata from the environment, config
// file, or channel setting. We create a "measurement_template"
// config set, so the configuration variables for our service are
// prefixed with "CALI_MEASUREMENT_TEMPLATE_". For example, set
// "CALI_MEASUREMENT_TEMPLATE_NAMES=a,b" to set "names" to "a,b".
ConfigSet config = channel->config().init("measurement_template", s_configdata);

// Read the "names" variable and treat it as a string list
// (comma-separated list). Returns a std::vector<std::string>.
auto names = config.get("names").to_stringlist();

// Create a MeasurementInfo entry for each of the "measurement
// variables" in the configuration.
for (const std::string& name : names)
m_info.push_back( create_measurement_info(c, channel, name) );
}

public:

// This is the entry function to initialize the service, specified
// in the CaliperService structure below. It is invoked when a Caliper
// channel using this service is created. A Caliper channel maintains
// a measurement configuration and the associated callbacks and data.
//
// Generally, a service can be enabled in multiple channels
// simultaneously. Each channel can have a different configuration, so
// each channel should use its own service instance. Implementors must
// take appropriate actions if an underlying API does not allow multiple
// clients or configurations, e.g. multiplexing access or erroring out
// when an instance already exists.
//
// This is the place to read in the service configuration, create
// any necessary objects like Caliper attributes, and register callback
// functions.

static void register_measurement_template_service(Caliper* c, Channel* channel) {
// Create a new service instance for this channel
MeasurementTemplateService* instance = new MeasurementTemplateService(c, channel);

// Register callback functions using lambdas
channel->events().post_init_evt.connect(
[instance](Caliper* c, Channel* channel){
instance->post_init_cb(c, channel);
});
channel->events().snapshot.connect(
[instance](Caliper* c, Channel* channel, int scopes, const SnapshotRecord* trigger_info, SnapshotRecord* rec){
instance->snapshot_cb(c, channel, scopes, trigger_info, rec);
});
channel->events().finish_evt.connect(
[instance](Caliper* c, Channel* channel){
// This callback is invoked when the channel is destroyed.
// No other callback will be invoked afterwards.
// Delete the channel's service instance here!
instance->finish_cb(c, channel);
delete instance;
});

Log(1).stream() << channel->name() << ": Registered measurement template service"
<< std::endl;
}
};

const ConfigSet::Entry MeasurementTemplateService::s_configdata[] = {
{ "names", // config variable name
CALI_TYPE_STRING, // datatype
"a,b", // default value
// short description
"Names of measurements to record",
// long description
"Names of measurements to record, separated by ','"
},
ConfigSet::Terminator
};

} // namespace [anonymous]

namespace cali
{

CaliperService measurement_template_service = {
"measurement_template",
::MeasurementTemplateService::register_measurement_template_service
};

} // namespace cali
30 changes: 30 additions & 0 deletions src/services/templates/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Caliper Service Templates

The service templates serve as templates, demos, and documentation for aspiring
Caliper service developers.

## MeasurementService

The MeasurementService template demonstrates a performance measurement service
(e.g., for reading hardware counters, timestamps, and other kinds of
performance data at runtime). It implements the `snapshot` callback for adding
data to Caliper snapshot records.

To try it out, build Caliper with testing turned on:

$ mkdir build && cd build
$ cmake -DBUILD_TESTING=On ..
$ make
$ make cxx-example

Try it using the cxx-example app:

$ CALI_SERVICES_ENABLE=event,measurement_template,trace,report \
CALI_MEASUREMENT_TEMPLATE_NAMES=test \
CALI_REPORT_CONFIG="select max(measurement.val.test),sum(measurement.test) group by prop:nested format tree" \
./examples/apps/cxx-example none
Path max#measurement.val.test sum#measurement.test
main 708 260
mainloop 684 220
foo 648 72
init 272 44
1 change: 1 addition & 0 deletions test/ci_app_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ set(PYTHON_SCRIPTS
test_monitor.py
test_multichannel.py
test_report.py
test_templateservices.py
test_textlog.py
test_thread.py
test_validator.py
Expand Down
2 changes: 1 addition & 1 deletion test/ci_app_tests/test_memusageservice.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# CpuInfo tests
# MemUsage tests

import unittest

Expand Down
Loading

0 comments on commit 0071b31

Please sign in to comment.