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'").
146 lines
4.8 KiB
C++
146 lines
4.8 KiB
C++
#include "build-result.hh"
|
||
#include "store-api.hh"
|
||
#include "misc.hh"
|
||
#include "util.hh"
|
||
#include "regex.hh"
|
||
|
||
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;
|
||
|
||
/* Compute the closure size. */
|
||
PathSet outputs;
|
||
for (auto & output : drv.outputs)
|
||
outputs.insert(output.second.path);
|
||
PathSet closure;
|
||
for (auto & output : outputs)
|
||
computeFSClosure(*store, output, closure);
|
||
for (auto & path : closure) {
|
||
auto info = store->queryPathInfo(path);
|
||
res.closureSize += info.narSize;
|
||
if (outputs.find(path) != outputs.end()) res.size += info.narSize;
|
||
}
|
||
|
||
/* Get build products. */
|
||
bool explicitProducts = false;
|
||
|
||
Regex regex(
|
||
"(([a-zA-Z0-9_-]+)" // type (e.g. "doc")
|
||
"[[:space:]]+"
|
||
"([a-zA-Z0-9_-]+)" // subtype (e.g. "readme")
|
||
"[[:space:]]+"
|
||
"(\"[^\"]+\"|[^[:space:]\"]+))" // path (may be quoted)
|
||
"([[:space:]]+([^[:space:]]+))?" // entry point
|
||
, true);
|
||
|
||
for (auto & output : outputs) {
|
||
Path failedFile = output + "/nix-support/failed";
|
||
if (pathExists(failedFile)) res.failed = true;
|
||
|
||
auto file = secureRead(output + "/nix-support/hydra-build-products");
|
||
if (!std::get<0>(file)) continue;
|
||
|
||
explicitProducts = true;
|
||
|
||
for (auto & line : tokenizeString<Strings>(std::get<1>(file), "\n")) {
|
||
BuildProduct product;
|
||
|
||
Regex::Subs subs;
|
||
if (!regex.matches(line, subs)) continue;
|
||
|
||
product.type = subs[1];
|
||
product.subtype = subs[2];
|
||
product.path = subs[3][0] == '"' ? string(subs[3], 1, subs[3].size() - 2) : subs[3];
|
||
product.defaultPath = subs[5];
|
||
|
||
/* Ensure that the path exists and points into the Nix
|
||
store. */
|
||
if (product.path == "" || product.path[0] != '/') continue;
|
||
try {
|
||
product.path = canonPath(product.path, true);
|
||
} catch (Error & e) { continue; }
|
||
if (!isInStore(product.path) || !pathExists(product.path)) continue;
|
||
|
||
/* FIXME: check that the path is in the input closure
|
||
of the build? */
|
||
|
||
product.name = product.path == output ? "" : baseNameOf(product.path);
|
||
|
||
struct stat st;
|
||
if (stat(product.path.c_str(), &st))
|
||
throw SysError(format("getting status of ‘%1%’") % product.path);
|
||
|
||
if (S_ISREG(st.st_mode)) {
|
||
product.isRegular = true;
|
||
product.fileSize = st.st_size;
|
||
product.sha1hash = hashFile(htSHA1, product.path);
|
||
product.sha256hash = hashFile(htSHA256, product.path);
|
||
}
|
||
|
||
res.products.push_back(product);
|
||
}
|
||
}
|
||
|
||
/* If no build products were explicitly declared, then add all
|
||
outputs as a product of type "nix-build". */
|
||
if (!explicitProducts) {
|
||
for (auto & output : drv.outputs) {
|
||
BuildProduct product;
|
||
product.path = output.second.path;
|
||
product.type = "nix-build";
|
||
product.subtype = output.first == "out" ? "" : output.first;
|
||
product.name = storePathToName(product.path);
|
||
|
||
struct stat st;
|
||
if (stat(product.path.c_str(), &st))
|
||
throw SysError(format("getting status of ‘%1%’") % product.path);
|
||
if (S_ISDIR(st.st_mode))
|
||
res.products.push_back(product);
|
||
}
|
||
}
|
||
|
||
/* Get the release name from $output/nix-support/hydra-release-name. */
|
||
for (auto & output : outputs) {
|
||
Path p = output + "/nix-support/hydra-release-name";
|
||
if (!pathExists(p)) continue;
|
||
try {
|
||
res.releaseName = trim(readFile(p));
|
||
} catch (Error & e) { continue; }
|
||
// 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;
|
||
}
|