Skip to content

Commit

Permalink
Merge pull request awslabs#19 from jboynes/thread_metrics
Browse files Browse the repository at this point in the history
Simplify thread local metrics
  • Loading branch information
bradboswell authored Jun 20, 2018
2 parents e05f2c3 + a02ecc8 commit 7193ce1
Show file tree
Hide file tree
Showing 43 changed files with 876 additions and 973 deletions.
10 changes: 10 additions & 0 deletions metrics-api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,22 @@
<artifactId>type-safe</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>software.amazon.swage</groupId>
<artifactId>thread-context</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>
64 changes: 64 additions & 0 deletions metrics-api/src/examples/java/Sample.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import software.amazon.swage.collection.TypedMap;
import software.amazon.swage.metrics.ContextData;
import software.amazon.swage.metrics.Metric;
import software.amazon.swage.metrics.MetricContext;
import software.amazon.swage.metrics.ThreadLocalMetrics;
import software.amazon.swage.metrics.Unit;
import software.amazon.swage.metrics.record.MetricRecorder;
import software.amazon.swage.metrics.record.NullRecorder;
import software.amazon.swage.threadcontext.ThreadContext;

/**
* Sample code that illustrates the different actors in the system.
*/
public class Sample {
private static final Metric NANO_TIME = Metric.define("Time");
private static final Metric CALL = Metric.define("Call");

public static void main(String[] args) {
// Role 1: Set up the metric recording system.
MetricRecorder recorder = new NullRecorder();

// Role 2: Set up the context in which measurements will be taken.
TypedMap metadata = ContextData.withId("id").build();
try (MetricContext context = recorder.context(metadata)) {

// Call a task that is aware of the MetricContext
contextAware(context);

// Options for calling code that is not aware of the metrics framework
// First we add the MetricContext to the ThreadContext that will be used when
// application code is called. Typically any other Thread-associated keys would
// be added at the same time.
ThreadContext threadContext = ThreadContext.emptyContext()
.with(ThreadLocalMetrics.METRIC_CONTEXT, context);

// Associating the MetricContext aroud a block of code.
try (ThreadContext.CloseableContext ig = threadContext.open()) {
applicationCode();
}

// Or just using a wrapper if we're simply invoking a method.
threadContext.wrap(Sample::applicationCode).run();
}
}

// Role 3: A task that emits metrics but is passed the MetricContext to use.
private static void contextAware(MetricContext context) {
context.record(NANO_TIME, System.nanoTime(), Unit.NONE);
context.count(CALL);

}
// Role 3: A task that emits metrics, getting the context from the Thread.
// The application code is not aware of metrics being collected.
private static void applicationCode() {
libraryCode();
}

// But the library it uses emits metrics.
private static void libraryCode() {
MetricContext context = ThreadLocalMetrics.current();
context.record(NANO_TIME, System.nanoTime(), Unit.NONE);
context.count(CALL);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,33 @@
import software.amazon.swage.collection.TypedMap;

/**
* Builder for data carried with the context under which a metric is reported.
* Builder for attributes carried with the context under which a metric is reported.
*
* <p>
* Roughly equivalent to a request context, this is the metadata associated with
* Roughly equivalent to a request context, this is the attributes associated with
* a metric context that allows relations and aggregations between events.
* Things like request id, tracing id, etc. will be carried here.
*
* <p>
* Built instances are immutable. Metric recording will use
* the data across threads as needed, and relies on the data being
* the attributes across threads as needed, and relies on the attributes being
* unchanged for a particular Context.
*
* <p>
* Different application domains may extend the builder to facilitate adding
* the appropriate data.
* the appropriate attributes.
*
* A domain-specific data builder might look like:
* A domain-specific attributes builder might look like:
* <pre>{@code
public class DomainSpecificData extends ContextData {
public static final TypedMap.Key<String> MARKETPLACE = TypedMap.key("marketplace", String.class);
public static final TypedMap.Key<String> SERVICE = TypedMap.key("serviceName", String.class);
public static final TypedMap.Key<String> OPERATION = TypedMap.key("operation", String.class);
public static final TypedMap.Key<String> PROGRAM = TypedMap.key("programName", String.class);
public static DomainSpecificData from(TypedMap data) {
public static DomainSpecificData from(TypedMap attributes) {
DomainSpecificData b = new DomainSpecificData();
data.iterator().forEachRemaining((e) -> b.add(e.getKey(), e));
attributes.iterator().forEachRemaining((e) -> b.add(e.getKey(), e));
return b;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package software.amazon.swage.metrics;

import java.time.Instant;

import software.amazon.swage.collection.TypedMap;

/**
* The context in which a measurement is taken. A {MetricContext} contains the parameters of
* the environment in which a measurement is taken.
* In the physical world, these might define the effective time and place;
* in a computer system, they might define the host, request, function, task or
* other application-specific parameters.
* <p/>
* A {MetricContext} is the primary interface used to record measurements.
*/
// TODO: provide a childContext() mechanism?
public interface MetricContext extends AutoCloseable {
/**
* Returns the attributes and their values that identity the context in which measurements
* are being taken.
*
* @return the Context attributes
*/
TypedMap attributes();

/**
* Record the value of a specific metric in this context, as gauged at a specific time.
*
* @param label the metric being recorded
* @param value gauged value, with units.
* @param unit type of the value, e.g. seconds, percent, etc.
* @param time when the value was sampled
*/
void record(Metric label, Number value, Unit unit, Instant time);

/**
* Record a measurement at the current time.
* <p/>
* Equivalent to calling record(metric, value, unit, Instant.now())
*
* @param label the metric being recorded
* @param value gauged value, with units.
* @param unit type of the value, e.g. seconds, percent, etc.
*/
default void record(Metric label, Number value, Unit unit) {
record(label, value, unit, Instant.now());
}

/**
* Count the increase or decrease of a metric in the given context.
* <p/>
* Records a change in value of a metric in the scope of this context. This
* is often used to record occurrences of an event, such as the number of times
* a method is called, an error occurred, or the amount of data sent.
* <p/>
* Changes to the count are not timestamped as only the total value of all
* counts for a metric have any meaning. If the individual change needs to
* be tracked, it should be recorded as a gauged event.
*
* @param label the metric being recorded
* @param delta the change in the value
*/
void count(Metric label, long delta);

/**
* Count a single occurrence of a metric.
* <p/>
* Equivalent to calling count(label, 1).
*
* @param label the metric that occurred
*/
default void count(Metric label) {
count(label, 1L);
}

/**
* Close this context, indicating that no more measurements will be taken.
*/
@Override
void close();
}

Loading

0 comments on commit 7193ce1

Please sign in to comment.