Add support for tracking custom metrics

Builds can now emit metrics that Hydra will store in its database and
render as time series via flot charts. Typical applications are to
keep track of performance indicators, coverage percentages, artifact
sizes, and so on.

For example, a coverage build can emit the coverage percentage as
follows:

  echo "lineCoverage $pct %" > $out/nix-support/hydra-metrics

Graphs of all metrics for a job can be seen at

  http://.../job/<project>/<jobset>/<job>#tabs-charts

Specific metrics are also visible at

  http://.../job/<project>/<jobset>/<job>/metric/<metric>

The latter URL also allows getting the data in JSON format (e.g. via
"curl -H 'Accept: application/json'").
This commit is contained in:
Eelco Dolstra
2015-07-31 00:57:30 +02:00
parent 8092149a9f
commit 4d26546d3c
18 changed files with 437 additions and 30 deletions

View File

@ -7,6 +7,21 @@
using namespace nix;
static std::tuple<bool, string> secureRead(Path fileName)
{
auto fail = std::make_tuple(false, "");
if (!pathExists(fileName)) return fail;
try {
/* For security, resolve symlinks. */
fileName = canonPath(fileName, true);
if (!isInStore(fileName)) return fail;
return std::make_tuple(true, readFile(fileName));
} catch (Error & e) { return fail; }
}
BuildOutput getBuildOutput(std::shared_ptr<StoreAPI> store, const Derivation & drv)
{
BuildOutput res;
@ -40,22 +55,12 @@ BuildOutput getBuildOutput(std::shared_ptr<StoreAPI> store, const Derivation & d
Path failedFile = output + "/nix-support/failed";
if (pathExists(failedFile)) res.failed = true;
Path productsFile = output + "/nix-support/hydra-build-products";
if (!pathExists(productsFile)) continue;
auto file = secureRead(output + "/nix-support/hydra-build-products");
if (!std::get<0>(file)) continue;
explicitProducts = true;
/* For security, resolve symlinks. */
try {
productsFile = canonPath(productsFile, true);
} catch (Error & e) { continue; }
if (!isInStore(productsFile)) continue;
string contents;
try {
contents = readFile(productsFile);
} catch (Error & e) { continue; }
for (auto & line : tokenizeString<Strings>(contents, "\n")) {
for (auto & line : tokenizeString<Strings>(std::get<1>(file), "\n")) {
BuildProduct product;
Regex::Subs subs;
@ -122,5 +127,19 @@ BuildOutput getBuildOutput(std::shared_ptr<StoreAPI> store, const Derivation & d
// FIXME: validate release name
}
/* Get metrics. */
for (auto & output : outputs) {
auto file = secureRead(output + "/nix-support/hydra-metrics");
for (auto & line : tokenizeString<Strings>(std::get<1>(file), "\n")) {
auto fields = tokenizeString<std::vector<std::string>>(line);
if (fields.size() < 2) continue;
BuildMetric metric;
metric.name = fields[0]; // FIXME: validate
metric.value = atof(fields[1].c_str()); // FIXME
metric.unit = fields.size() >= 3 ? fields[2] : "";
res.metrics[metric.name] = metric;
}
}
return res;
}