Metric Provider Developement Guide

In this tutorial we will implement two metric providers related to the commits that occur in a version control system. The first, a transient metric provider, will keep track of the dates, times, and messages of all commits in the repositories. The second, a historical metric provider, will count the total number of commits over time.

We'll go through the steps above for each metric provider.

Pre-requisites

The Transient Metric Provider

This metric provider will store a complete history of the commits in the version control system(s) used by a project.

0. Setup

Create a new Plugin project in Eclipse.

1. The data model

We define the data model using the Emfatic language. In your newly created plug-in, create a package called org.ossmeter.metricprovider.trans.commits.model. In that package create an empty file called commits.emf. In this file, we will define our data model.

First of all, we need to state the name of the package.

package org.ossmeter.metricprovider.trans.commits.model;

This is used by the Pongo code generator - the generated classes will be put in this package.

We then define the database for our model:

@db(qualifiedCollectionNames="true")
class Commits {
    val Repository[*] repositories;
    val Commit[*] commits;
}

The @db annotation tells Pongo that this will be the container database for the data. Adding the qualifiedCollectionNames=true property will prepend the database name to all Mongo collections.

The Commits class above says that we want a database with two collections, name repositories and commits. If qualifiedCollectionNames is set to true, the collections will be named Commits.repositories and Commits.commits.

We now define the schema of the Commits.repositories collection:

~~~java class Repository { @searchable attr String url; attr String repoType; attr String revision; attr int totalNumberOfCommits; ref CommitData[*] commits; }


This class is used to store information related to the commits in the project's VCS repositories. The collection will contain one entry per VCS repository. Repositories are identified by a `url`, and also have a `repoType` (e.g. Git or SVN), the latest `revision`, the `totalNumberOfCommits`, and a reference to all of the `commits` in the repository, which are stored in the `Commits.commits` collection.

The `@searchable` annotation will make Pongo generate two utility search methods on the collection: `findByUrl(String url) : Iterable<Repository>` and `findOneByUrl(String url) : Repository`. An index will also be create on this field to improve look up time.

Each commit is represented in the `Commits.commits` collection by the following model:

~~~~java
class Commit {
    @searchable
    attr Date date;
    attr String identifier;
    attr String message;
    attr String author;
}

For each commit, we store its date, identifier (revision ID), the commit message, and the author. We also create an index on the date to allow us to quickly discover the set of commits that occurred on a given date (using the autogenerated findByDate(String date) or findOneByDate(String date) methods).

Now we need to use Pongo to generate code from this model. Right-click on the commits.emf file and select Pongo > Generate Pongos and plugin.xml. You should see some Java classes appear in your package.

Now that we have our data model, we can implement the metric provider.

2. The metric provider

Create a Java class called org.ossmeter.metricprovider.trans.commits.CommitsTransientMetricProvider

This class should extend AbstractTransientMetricProvider and specify Commits for the generic argument:

public class org.ossmeter.metricprovider.trans.commits.CommitsTransientMetricProvider extends AbstractTransientMetricProvider<Commits>

The generic argument states that the metric provider stores objects conforming to the Commits data model. Note: You do not need to extend AbstractTransientMetricProvider and can implement ITransientMetricProvider instead should you wish to.

There are a number of methods that need implementing. We will discuss each in turn.

adapt(DB db)

This method returns the Pongo database that the metric provider will use to store its data. This is boilerplate, unfortunately, we can't auto-generate it. Implement as follows:

    @Override
    public Commits adapt(DB db) {
        return new Commits(db);
    }

The next thing we want to do is fill in useful information that helps users understand the purpose of the metric provider:

    @Override
    public String getShortIdentifier() { // This may be deprecated very soon
        return "transient-commits"; 
    }

    @Override
    public String getFriendlyName() {
        return "Commit History";
    }

    @Override
    public String getSummaryInformation() {
        return "The commit history of the project.";
    }

The next method allows you to declare whether the metric provider is applicable to a given project:

@Override
    public boolean appliesTo(Project project) {
        return project.getVcsRepositories().size() > 0;
    }

Our metric applies to any project that has at least one VCS repository.

Finally, we have the measure(...) method that performs the actual metric calculation:

    @Override
    public void measure(Project project, ProjectDelta delta, Commits db) {

        for (VcsRepositoryDelta repoDelta : delta.getVcsDelta().getRepoDeltas()) {
            Repository repo = db.getRepositories().findOneByUrl(repoDelta.getRepository().getUrl());

            if (repo == null) {
                repo = new Repository();
                repo.setUrl(repoDelta.getRepository().getUrl());
                db.getRepositories().add(repo);
            }

            for (VcsCommit commit : repoDelta.getCommits()) {
                Commit c = new Commit();
                c.setDate(commit.getJavaDate());
                c.setMessage(commit.getMessage());
                c.setAuthor(commit.getAuthor());
                c.setIdentifier(commit.getRevision());

                repo.getCommits().add(c);
                db.getCommits().add(c);
            }
        }
        db.getCommits().sync();
        db.getRepositories().sync();
    }

The above method iterates through the delta computed by the platform. The delta consists of the commits that occurred in each of the project's VCS repositories for the date being analysed. A new Commit object is created for each commit in the delta.

3. Make the metric provider discoverable

Metric providers are registered with the OSSMETER platform using extension points:

Now everything is ready for the metric to be executed :)

The Historic Metric Provider

This metric provider will keep track of the total number of commits of the project over time. For each date of the project, the metric counts how many commits have been made and stores the value.

0. Setup

Create a new Plugin project in Eclipse.

1. The data model

In your newly created plug-in, create a package called org.ossmeter.metricprovider.historic.commits.model. In that package create an empty file called historiccommits.emf. In this file, we will define our data model:

package org.ossmeter.metricprovider.historic.commits.model;

class HistoricCommits {
    attr int numberOfCommits;
}

The data model for the historic metric is much simpler. No Pongo annotations are used because, unlike transient metrics, the platform is responsible for storing the historic data. The data model defined here will be stored against the date that the analysis is performed.

2. The metric provider

Create a Java class called org.ossmeter.metricprovider.historical.commits.CommitsHistoricalMetricProvider

This class should extend AbstractHistoricalMetricProvider (there are no generic arguments (yet!)):

public class org.ossmeter.metricprovider.trans.commits.CommitsTransientMetricProvider extends AbstractTransientMetricProvider The generic argument states that the metric provider stores objects conforming to the Commits data model. Note: You do not need to extend AbstractTransientMetricProvider and can implement ITransientMetricProvider instead should you wish to.

There are a number of methods that need implementing. We will discuss each in turn.

First of all, complete the typical information-related methods:

    @Override
    public String getShortIdentifier() {
        return "historicalcommits";
    }

    @Override
    public String getFriendlyName() {
        return "Historical commits";
    }

    @Override
    public String getSummaryInformation() {
        return "...";
    }

Now complete the standard appliesTo method:

    @Override
    public boolean appliesTo(Project project) {
        return project.getVcsRepositories().size() > 0;
    }

We now need to specify a dependency on the transient metric provider that we just implemented.

    @Override
    public List<String> getIdentifiersOfUses() {
        return Arrays.asList(CommitsTransientMetricProvider.class.getCanonicalName());
    }   

This tells the platform that we need access to the CommitsTransientMetricProvider database. The platform will assign this to the uses field that is available to the historical metric provider, as you'll see in the measure method:

    @Override
    public Pongo measure(Project project) {
        Commits transDb = (Commits) getDbOfMetricProvider(project, (ITransientMetricProvider) uses.get(0));

        int commits = (int) transDb.getCommits().size();

        HistoricCommits hist = new HistoricCommits();
        hist.setNumberOfCommits(commits);

        return hist;
    }

First of all, we get hold of the database of the transient commits metric provider, and simply count the size of the commits collection. We save this in an instance of the HistoricCommits Pongo defined above. This object is returned by the method and the platform stores it in the database along with the date that is currently being analysed for the project.

3. Make the metric provider discoverable

This process is the same as the transient metric provider:

Now everything is ready for both metrics to be executed :)

But first. Let's specify how we want this historical metric to be visualised.

4. Define a MetVis visualisation specification

MetVis is a JSON-based specification language and visualisation engine for metrics. You specify how the Pongo data model should be transformed into something that can be plotted on a chart. The MetVis web page has numerous examples of this.

Create a folder in your historical metric project called 'vis'. In the 'vis' folder create a file called 'historicalcommits.json'. Here is the MetVis specification for the historical commits metric provider:

{
    "metricid" : "org.ossmeter.metricprovider.historic.commits.CommitsHistoricalMetricProvider",
    "vis" : [
        {
            "id" : "historicalcommits",
            "name" : "Commits over time",
            "description" : "This metric shows when the projects commits occurred",
            "type" : "LineChart",
            "datatable" : {
                "cols" : [
                    { "name" : "Date", "field" : "$__date" },
                    { "name" : "Commits", "field" : "$numberOfCommits" }
                ]
            },
            "x" : "Date",
            "y" : "Commits"
        }
    ]
}

The metricId field tells the platform which metric provider the specification visualises. A metric provider can have multiple visualisations. In this case, we just define one, which plots the date on the X-axis and the number of commits on the Y-axis. Fields in the Pongo data model are references using the $-sign. To access the date field of a historical metric, use $__date. The final two fields (x and y) are references to column names in the datatable specification. The type states that the data should be plotted as a line chart. You can test your MetVis specifications on the MetVis playpen.

5. Make the visualisation specification discoverable

As with metric providers, visualisation specifications are registered using extension points.

Good job.

Running the metric providers

See Running from Source

Homework

Adapt the historical commits metric provider so that it stores the commits for each repository separately (in the above, it sums them all up). Write the MetVis specification - each series should be the commits for separate repositories.