5.4. Defining Telemetry Streams

5.4.1. Telemetry Streams Syntax

To define a new Telemetry Streams instance, you supply a Telemetry Streams definition with the following syntax:

streams <StreamsName> <ParameterList> = { <DocumentationString>, <ReductionFunctionExpression> } ;

<StreamsName> is an identifier conforming to general Java syntax for identifier names (no embedded whitespace, weird characters, etc.) Also, the telemetry infrastructure maintains a global namespace for Streams, Charts, Y-Axes, and Reports, so the identifier chosen must be unique across the entire server. Examples of legal <StreamsNames> include "ActiveTimeStream" and "JavaLOCStream". StreamsNames are case-sensitive. Although the name of a Streams instance doesn't have to end with "Stream", this is helpful for maintaining uniqueness given the single global namespace.

<ParameterList> is a possibly empty list of comma-separated identifiers defining the parameters for this Streams instance. Parameters allow the calling entity (normally a Telemetry Chart) to pass in a value to be provided to the Reduction Function(s) referenced inside the Streams definition.

<DocumentationString> is a string that provides information about the purpose or behavior of this Streams instances. An example of a legal <DocumentationString> is: "Active Time in top-level workspaces".

<ReductionFunctionExpression> can be thought of as having three flavors: (1) the "simple" <ReductionFunctionExpression> which consists of a single invocation of a Reduction Function; (2) the "compound" <ReductionFunctionExpression> which is an expression whose operators are "+", "-", "/", and "*", and whose operands are Reduction Function invocations; and (3) the "filtered" <ReductionFunctionExpression>, in which a <ReductionFunctionExpression> is passed as the argument to a Telemetry Filter Function, which generally serves to return a subset of the streams represented by the <ReductionFunctionExpression> according to some criteria. To make this concrete, let's look at some examples.

Here is an example of a "simple" <ReductionFunctionExpression>: this single Reduction Function returns a Streams instance containing multiple streams, where each stream represents the cumulative Active Time for a single Project member over time.

MemberActiveTime("**", "cumulative")

Here is an example of a "compound" <ReductionFunctionExpression>: this arithmetic expression returns a Streams instance containing a single stream with the total number of lines added and deleted for a single Project member:

(MemberCodeChurn("LinesAdded", "**", "johnson@hawaii.edu") + MemberCodeChurn("LinesDeleted", "**", "johnson@hawaii.edu"))

Finally, here is an example of a "filtered" <ReductionFunctionExpression>: this expression returns a Streams instance containing those streams that subset of Project members with non-zero Active Time:

FilterZero(MemberActiveTime("**", "cumulative"))

5.4.2. Telemetry Streams Examples

Let's put these syntactic components together to illustrate some stream definitions. The following example shows a very simple definition without parameters:

streams ProjectCumulativeMemberActiveTimeStream() = {
  "Computes streams for each project member with cumulative active time.", 
  ProjectMemberActiveTime("**", "cumulative")
};

The following example shows the use of an arithmetic expression to provide a Streams instance that computes the code churn "rate" by first adding together the lines added and deleted, then dividing by the Active Time associated with that time interval.

streams CodeChurnRateStream() = {
  "The total lines added plus deleted from configuration management per minute of Active Time.", 
  (CodeChurn("LinesAdded") + CodeChurn("LinesDeleted")) / ActiveTime("**"))
};

The following example shows the use of parameters to provide a Streams instance that can be parameterized in different ways when included in Charts or Reports.

streams MemberCodeChurnStream (filePattern, memberEmail) = {
  "The total lines added plus deleted by the specified member for the specified file pattern.", 
  (MemberCodeChurn("LinesAdded", filePattern, memberEmail) + 
   MemberCodeChurn("LinesDeleted", filePattern, memberEmail))
};

Finally, let's assume we have defined a Project with dozens of members, but only a few of them are actively programming at any one time. If we used the previous stream definition, then the resulting chart would contain many streams and a very large legend, even though most of those lines would have no values. The following example shows the use of a Filter Function to provide a Streams instance that shows only those members with non-zero code churn:

streams MemberCodeChurnStream (filePattern) = {
  "The total lines added plus deleted by the specified member for the specified file pattern.", 
  FilterZero(
    MemberCodeChurn("LinesAdded", filePattern, "*") +   
    MemberCodeChurn("LinesDeleted", filePattern, "*")
  )
};

5.4.3. Telemetry Streams Arithmetic

Some care must be used when performing arithmetic operations on Streams to ensure that the results obtained make sense. Each Reduction Function returns an instance of a TelemetryStreamCollection. When you specify that two invocations of a Reduction Function are to be added together, what happens is that the Reduction Functions are invoked, and the resulting two TelemetryStreamCollections are passed to an "ADD" method that performs a pair-wise addition of the interior data points and returns a new TelemetryStreamsCollection instance with the results. In this case, the number of elements in each TelemetryStreamCollection must be the same.

This algorithm works as expected for "obvious" kinds of addition when the two Reduction Function invocations are of the same type, such as:

CodeChurn("LinesAdded") + CodeChurn("LinesDeleted")

The arithmetic expression evaluation mechanism also handles constants. In this case, each element in the Collection is computed upon by the constant individually. For example, consider the following <ReductionFunctionExpression>:

(JavaCoverage("NumOfCoveredMethods", "**") / 
   (JavaCoverage("NumOfCoveredMethods", "**") + JavaCoverage("NumOfUnCoveredMethods", "**"))
* 100

In this case, we first compute the percentage of covered methods for this Project during each of the specified time intervals as a fraction between 0 and 1, then multiply each by 100 to get a percentage between 0 and 100.

Unfortunately, there is nothing to prevent you from creating "nonsense" values when performing arithmetic. Consider the following:

CodeChurn("LinesAdded") + ActiveTime("**")

The Telemetry Definition Language does not have any kind of "type" system which would recognize that Code Churn and Active Time cannot be added together. Of course, you will hopefully realize that this arithmetic expression does not make sense when you attempt to define a y-axis instance and declare a meaningful "units" string for such a "bogus" expression.

5.4.4. Defining Telemetry Streams with the Telemetry Management Console

To define Telemetry Streams that are saved and can be made available to others, you invoke the Definition Management command located in the "Telemetry" section of the Preferences page. This returns a page containing sections listing the user-definable Telemetry constructs (Reports, Charts, Streams, and Y-Axes) available to this user. This command was previously illustrated in Figure 5.6, “ Definition Management Page ”.

The Definition Management page displays all of the Telemetry Streams to which you have access. If you are the owner of a Stream, then you can edit or delete it. The "new" button located at the top of the "Streams" section enables you to define a new streams instance. Figure 5.12, “ New Telemetry Streams Definition ” illustrates the page used to define a new Streams instances.

Figure 5.12.  New Telemetry Streams Definition


New Telemetry Streams Definition

As you can see, the new streams definition page contains a "template" definition to help you with the syntax of streams definition. It shows that a streams definition begins with the keyword "streams", followed by the name of the streams instance, followed by an argument list, then an equals sign and a left curly brace. This is all generally on the first line of the definition. The next line a documentation string describing what this streams instance returns, ended by a comma. The following lines contain the Reduction Function Expression, and the definition ends with a right curly brace and a semi-colon.

Along with the definition, you must also specify its visibility. There are three kinds of visibility: global (everyone can see it), not shared (only you can see it), or Project-local (visible only to members of a specific Project instance.) The pull-down list allows you to specify this visibility.

5.4.5. Exploring Streams Definitions with the Telemetry Expert Analysis command

The Telemetry Management Console allows you to define and manage your set of Telemetry Streams, but it does not provide a way for you to actually visualize the telemetry data they represent. If you would like to display the behavior of a Telemetry Streams definition prior to creating it, you can use the Telemetry Expert Analysis command, as illustrated in Figure 5.13, “ Telemetry Streams Visualization ”.

Figure 5.13.  Telemetry Streams Visualization


Telemetry Streams Visualization

The Telemetry Expert Analysis command provides selectors for the Project and Interval just like other telemetry display commands. However, instead of a pull-down menu of predefined Telemetry Charts or Reports, it provides a text box where you can interactively define Streams, Charts, Y-Axes, and Reports. In the text box in the example above, we define a single Telemetry Streams instance called "ReleasePlan_TotalIssues", which takes a single String parameter indicating the "fixVersion" for the Issues associated with this telemetry.

To display this Streams instance, we use the "draw" Telemetry Language construct, passing it the name of the Stream and any required parameters. The behavior of the "draw" construct, when passed a Telemetry Streams instance, is to implicitly create a Telemetry Chart instance containing the Telemetry Streams instance along with a default title and a default y-axis instance, then display that Chart.

The definitions made in the Telemetry Expert Analysis command last only for a single invocation. This enables you to easily experiment with different forms of Telemetry Streams by editing the contents of the text field and re-running the analysis. Once you are satisfied with the Streams instance you've defined, you can create a persistent definition of it using the Telemetry Management command as documented above.

Using the Telemetry Expert Analysis to visualize Streams provides you with a convenient means to explore the behavior of the available Reduction Functions and determine interesting parameter settings for a single Streams instance. However, this usage does not allow you to save or share your definitions, nor display more than one Streams instance in the same Chart. We will overcome these limitations in the next section, which documents the Telemetry Chart interface.