Compare commits

...

6 Commits

Author SHA1 Message Date
Lucas Bergman
94b05b1437 maint: NixOS module writes config to the Nix store
Instead of writing the config attrset to a YAML file to a fixed path in /etc,
this makes the NixOS module write to a (content-addressed) config file in the
Nix store. We just use builtins.toJSON since YAML is a superset of JSON.
2024-02-20 07:58:03 -06:00
Lucas Bergman
668015ed17 maint: Some Nix flake cleanups
Specifically:

  1. Switch from the nixosModule flake output (which is deprecated) to
     nixosModules.default
  2. Remove "with lib;" it's convenient, but I've gotten feedback when
     upstreaming module changes that that shouldn't be a thing in
     nixpkgs anymore
  3. Add a little type checking to the NixOS module options
  4. Switch the default config to empty (but leave the example)
  5. Use the convention "let cfg = config.services.heracles," a thing
     that's super common in NixOS
2024-02-20 07:53:13 -06:00
Lucas Bergman
63b4f810c2 maint: Set formatter Nix flake output so nix fmt works
I've idly thought about making the formatter a derivation that runs alejandra
over Nix code, then rustfmt over Rust code, etc--so that just running `nix
fmt` in the root cleans up everything--but I haven't made that work yet.
2024-02-19 20:32:19 -06:00
Lucas Bergman
fe162968e5 maint: Run nix stuff through the formatter
There are several choices for formatting Nix code, but IMHO alejandra is the
right kind of opinionated: <https://github.com/kamadorueda/alejandra>.
2024-02-19 20:31:47 -06:00
847413f4f5 fix: bug in scalar graph types 2024-02-19 19:25:36 -05:00
18eb50fbbd feat: use a js template literal for trace name 2024-02-19 19:15:34 -05:00
5 changed files with 144 additions and 169 deletions

View File

@ -1,11 +1,12 @@
let let
lock = builtins.fromJSON (builtins.readFile ./flake.lock); lock = builtins.fromJSON (builtins.readFile ./flake.lock);
in in
(import ( (import (
fetchTarball { fetchTarball {
url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
sha256 = lock.nodes.flake-compat.locked.narHash; sha256 = lock.nodes.flake-compat.locked.narHash;
} }
) { ) {
src = ./.; src = ./.;
}).defaultNix })
.defaultNix

View File

@ -8,10 +8,8 @@
- source: http://heimdall:9001 # Prometheus source uri for this plot - source: http://heimdall:9001 # Prometheus source uri for this plot
query: 'sum by (instance)(irate(node_cpu_seconds_total{job="nodestats"}[5m]))' # The PromQL query for this plot query: 'sum by (instance)(irate(node_cpu_seconds_total{job="nodestats"}[5m]))' # The PromQL query for this plot
meta: # metadata for this plot meta: # metadata for this plot
name_label: instance # Grab a trace name from the query tags name_format: "`${labels.instance}`" # javascript template literal to format the trace name
#d3_tick_format: "~%" # d3 tick format override for this plot's yaxis #d3_tick_format: "~%" # d3 tick format override for this plot's yaxis
#name_prefix: "Prefix" # A prefix for this sublots trace names
#name_suffix: "Suffix" # A suffix for this subplots trace names
#named_axis: "y" # yaxis name to use for this subplots traces #named_axis: "y" # yaxis name to use for this subplots traces
span: # The span for this range query span: # The span for this range query
end: now # Where the span ends. RFC3339 format with special handling for the now keyword end: now # Where the span ends. RFC3339 format with special handling for the now keyword
@ -32,16 +30,14 @@
sum by (instance)(irate(node_cpu_seconds_total{mode="system",job="nodestats"}[5m])) / sum by (instance)(irate(node_cpu_seconds_total{job="nodestats"}[5m])) sum by (instance)(irate(node_cpu_seconds_total{mode="system",job="nodestats"}[5m])) / sum by (instance)(irate(node_cpu_seconds_total{job="nodestats"}[5m]))
meta: meta:
d3_tick_format: "~%" d3_tick_format: "~%"
name_label: instance name_format: "`${labels.instance} system`"
name_prefix: "System"
named_axis: "y" named_axis: "y"
- source: http://heimdall:9001 - source: http://heimdall:9001
query: | query: |
sum by (instance)(irate(node_cpu_seconds_total{mode="user",job="nodestats"}[5m])) / sum by (instance)(irate(node_cpu_seconds_total{job="nodestats"}[5m])) sum by (instance)(irate(node_cpu_seconds_total{mode="user",job="nodestats"}[5m])) / sum by (instance)(irate(node_cpu_seconds_total{job="nodestats"}[5m]))
meta: meta:
d3_tick_format: "~%" d3_tick_format: "~%"
name_label: instance name_format: "`${labels.instance} user`"
name_suffix: "User"
named_axis: "y" named_axis: "y"
- title: Node memory - title: Node memory
query_type: Scalar query_type: Scalar
@ -49,4 +45,4 @@
- source: http://heimdall:9001 - source: http://heimdall:9001
query: 'node_memory_MemFree_bytes{job="nodestats"}' query: 'node_memory_MemFree_bytes{job="nodestats"}'
meta: meta:
name_label: instance name_format: "`${labels.instance}`"

231
flake.nix
View File

@ -6,149 +6,126 @@
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
naersk.url = "github:nix-community/naersk"; naersk.url = "github:nix-community/naersk";
flake-compat = { url = github:edolstra/flake-compat; flake = false; }; flake-compat = {
url = github:edolstra/flake-compat;
flake = false;
};
flake-utils.url = "github:numtide/flake-utils"; flake-utils.url = "github:numtide/flake-utils";
}; };
outputs = {nixpkgs, flake-utils, naersk, rust-overlay, ...}: outputs = {
nixpkgs,
flake-utils,
naersk,
rust-overlay,
...
}:
flake-utils.lib.eachDefaultSystem (system: let flake-utils.lib.eachDefaultSystem (system: let
overlays = [ overlays = [
rust-overlay.overlays.default rust-overlay.overlays.default
]; ];
pkgs = import nixpkgs { inherit system overlays; }; pkgs = import nixpkgs {inherit system overlays;};
rust-bin = pkgs.rust-bin.stable."1.71.0".default; rust-bin = pkgs.rust-bin.stable."1.71.0".default;
naersk-lib = pkgs.callPackage naersk { naersk-lib = pkgs.callPackage naersk {
rustc = rust-bin; rustc = rust-bin;
cargo = rust-bin; cargo = rust-bin;
}; };
heracles = naersk-lib.buildPackage { heracles = naersk-lib.buildPackage {
name = "heracles"; name = "heracles";
verion = "0.0.1"; verion = "0.0.1";
src = ./.; src = ./.;
nativeBuildInputs = [ pkgs.pkg-config ]; nativeBuildInputs = [pkgs.pkg-config];
buildInputs = ( buildInputs =
if pkgs.stdenv.isDarwin then (
with pkgs.darwin.apple_sdk.frameworks; [ Security SystemConfiguration ] if pkgs.stdenv.isDarwin
else then with pkgs.darwin.apple_sdk.frameworks; [Security SystemConfiguration]
[ pkgs.openssl ]) ++ [rust-bin]; else [pkgs.openssl]
)
++ [rust-bin];
}; };
in in {
{
packages.default = heracles; packages.default = heracles;
}) // { formatter = pkgs.alejandra;
nixosModule = {config, pkgs, lib}: with lib; { })
options = { // {
services.heracles.enable = mkEnableOption "enable heracles service"; nixosModules.default = {
services.heracles.listen = mkOption { config,
description = "[host]:port address for heracles to listen on"; pkgs,
default = "localhost:8080"; lib,
defaultText = "localhost:8080"; }: {
}; options = {
services.heracles.enable = lib.mkEnableOption "enable heracles service";
services.heracles.listen = lib.mkOption {
description = "[host]:port address for heracles to listen on";
default = "localhost:8080";
defaultText = "localhost:8080";
type = lib.types.string;
};
services.heracles.settings = mkOption { services.heracles.settings = lib.mkOption {
description = "heracles dashboard Configuration"; description = "heracles dashboard Configuration";
default = [ type = lib.types.listOf lib.types.attrs;
default = [];
defaultText = lib.literalExpression ''
[
{
title = "A dashboard";
graphs = [
{
title = "Graph title";
query_type = "Range";
# yaxis formatting default for this graph
d3_tick_format = "~s";
plots = [
{ {
title = "A dashboard"; source = "http://heimdall:9001";
graphs = [ query = \'\'
{ sum by (instance)(irate(node_cpu_seconds_total{job="nodestats"}[5m]))
title = "Graph title"; \'\';
query_type = "Range"; meta = {
# yaxis formatting default for this graph name_function = "''${labels.instance}";
d3_tick_format = "~s"; named_axis = "y";
plots = [ # yaxis formatting for this subplot
{ d3_tick_format = "~s";
source = "http://heimdall:9001"; };
query = ''
sum by (instance)(irate(node_cpu_seconds_total{job="nodestats"}[5m]))
'';
meta = {
name_label = "instance";
name_prefix = "trace name prefix";
name_suffix = "trace name suffix";
named_axis = "y";
# yaxis formatting for this subplot
d3_tick_format = "~s";
};
}
];
# span for this graph.
span = {
end = "now";
duration = "1d";
step_duration = "10min";
};
}
];
# default span for dashboard
span = {
end = "now";
duration = "1d";
step_duration = "10min";
};
} }
]; ];
defaultText = '' # span for this graph.
[ span = {
{ end = "now";
title = "A dashboard"; duration = "1d";
graphs = [ step_duration = "10min";
{ };
title = "Graph title"; }
query_type = "Range"; ];
# yaxis formatting default for this graph # default span for dashboard
d3_tick_format = "~s"; span = {
plots = [ end = "now";
{ duration = "1d";
source = "http://heimdall:9001"; step_duration = "10min";
query = \'\' };
sum by (instance)(irate(node_cpu_seconds_total{job="nodestats"}[5m])) }
\'\'; ]
meta = { '';
name_label = "instance"; };
name_prefix = "trace name prefix";
name_suffix = "trace name suffix";
named_axis = "y";
# yaxis formatting for this subplot
d3_tick_format = "~s";
};
}
];
# span for this graph.
span = {
end = "now";
duration = "1d";
step_duration = "10min";
};
}
];
# default span for dashboard
span = {
end = "now";
duration = "1d";
step_duration = "10min";
};
}
]
'';
};
};
config = mkIf config.services.heracles.enable {
environment.etc."heracles.yaml" = {
text = (generators.toYAML {} config.services.heracles.settings);
};
systemd.services.heracles = {
wantedBy = [ "multi-user.target" "default.target" ];
wants = [ "network.target" ];
after = [ "network-online.target" ];
serviceConfig = {
Restart = "on-failure";
RestartSec = "30s";
ExecStart = "${pkgs.heracles}/bin/heracles --listen ${config.services.heracles.listen} --config=${config.environment.etc."heracles.yaml".target}";
};
};
};
}; };
config = let
cfg = config.services.heracles;
cfgFile = pkgs.writeText "heracles.yaml" (builtins.toJSON cfg.settings);
in
lib.mkIf cfg.enable {
systemd.services.heracles = {
wantedBy = ["multi-user.target" "default.target"];
wants = ["network.target"];
after = ["network-online.target"];
serviceConfig = {
Restart = "on-failure";
RestartSec = "30s";
ExecStart = "${pkgs.heracles}/bin/heracles --listen ${cfg.listen} --config=${cfgFile}";
};
};
};
};
}; };
} }

View File

@ -117,9 +117,7 @@ pub struct DataPoint {
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PlotMeta { pub struct PlotMeta {
name_prefix: Option<String>, name_format: Option<String>,
name_suffix: Option<String>,
name_label: Option<String>,
named_axis: Option<String>, named_axis: Option<String>,
d3_tick_format: Option<String>, d3_tick_format: Option<String>,
} }

View File

@ -160,19 +160,17 @@ class TimeseriesGraph extends HTMLElement {
yaxis: yaxis, yaxis: yaxis,
yhoverformat: meta["d3_tick_format"], yhoverformat: meta["d3_tick_format"],
}; };
const namePrefix = meta["name_prefix"];
const nameSuffix = meta["name_suffix"];
const nameLabel = meta["name_label"];
var name = ""; var name = "";
if (namePrefix) { const formatter = meta.name_format
name = namePrefix + "-"; if (formatter) {
}; name = eval(formatter);
if (nameLabel && labels[nameLabel]) { } else {
name = name + labels[nameLabel]; var names = [];
}; for (const value of labels) {
if (nameSuffix) { names.push(value);
name = name + " - " + nameSuffix; }
}; name = names.join(" ");
}
if (name) { trace.name = name; } if (name) { trace.name = name; }
for (const point of series) { for (const point of series) {
trace.x.push(new Date(point.timestamp * 1000)); trace.x.push(new Date(point.timestamp * 1000));
@ -190,17 +188,22 @@ class TimeseriesGraph extends HTMLElement {
type: "bar", type: "bar",
x: [], x: [],
y: [], y: [],
yaxis: yaxis,
yhoverformat: meta["d3_tick_format"], yhoverformat: meta["d3_tick_format"],
}; };
let nameLabel = meta["name_label"]; const formatter = meta.name_format;
if (nameLabel && labels[nameLabel]) { var name = "";
trace.name = labels[nameLabel]; if (formatter) {
}; name = eval(formatter);
if (nameLabel && labels[nameLabel]) { } else {
trace.x.push(labels[nameLabel]); var names = [];
}; for (const value of labels) {
names.push(value);
}
name = names.join(" ");
}
if (name) { trace.name = name; }
trace.y.push(series.value); trace.y.push(series.value);
trace.x.push(trace.name);
traces.push(trace); traces.push(trace);
} }
} }