ui: visualize lnd lightning report on the tab panel
similarly to 0260d477
, the lightning tab has now some basic info
including channels list.
the gui playground is updated to send some stub data via comms
periodically.
pull/26/head
parent
52a8c1fb1a
commit
05c89bbd1c
21
src/ngui.zig
21
src/ngui.zig
|
@ -42,6 +42,7 @@ var last_report: struct {
|
|||
mu: std.Thread.Mutex = .{},
|
||||
network: ?comm.ParsedMessage = null, // NetworkReport
|
||||
bitcoind: ?comm.ParsedMessage = null, // BitcoinReport
|
||||
lightning: ?comm.ParsedMessage = null, // LightningReport
|
||||
|
||||
fn deinit(self: *@This()) void {
|
||||
self.mu.lock();
|
||||
|
@ -54,6 +55,10 @@ var last_report: struct {
|
|||
v.deinit();
|
||||
self.bitcoind = null;
|
||||
}
|
||||
if (self.lightning) |v| {
|
||||
v.deinit();
|
||||
self.lightning = null;
|
||||
}
|
||||
}
|
||||
|
||||
fn replace(self: *@This(), new: comm.ParsedMessage) void {
|
||||
|
@ -73,6 +78,12 @@ var last_report: struct {
|
|||
}
|
||||
self.bitcoind = new;
|
||||
},
|
||||
.lightning_report => {
|
||||
if (self.lightning) |old| {
|
||||
old.deinit();
|
||||
}
|
||||
self.lightning = new;
|
||||
},
|
||||
else => |t| logger.err("last_report: replace: unhandled tag {}", .{t}),
|
||||
}
|
||||
}
|
||||
|
@ -220,8 +231,10 @@ fn commThreadLoopCycle() !void {
|
|||
switch (state) {
|
||||
.standby => switch (msg.value) {
|
||||
.ping => try comm.write(gpa, stdout, comm.Message.pong),
|
||||
.network_report => last_report.replace(msg),
|
||||
.bitcoind_report => last_report.replace(msg),
|
||||
.network_report,
|
||||
.bitcoind_report,
|
||||
.lightning_report,
|
||||
=> last_report.replace(msg),
|
||||
else => logger.debug("ignoring {s}: in standby", .{@tagName(msg.value)}),
|
||||
},
|
||||
.active, .alert => switch (msg.value) {
|
||||
|
@ -238,6 +251,10 @@ fn commThreadLoopCycle() !void {
|
|||
ui.bitcoin.updateTabPanel(rep) catch |err| logger.err("bitcoin.updateTabPanel: {any}", .{err});
|
||||
last_report.replace(msg);
|
||||
},
|
||||
.lightning_report => |rep| {
|
||||
ui.lightning.updateTabPanel(rep) catch |err| logger.err("lightning.updateTabPanel: {any}", .{err});
|
||||
last_report.replace(msg);
|
||||
},
|
||||
else => logger.warn("unhandled msg tag {s}", .{@tagName(msg.value)}),
|
||||
},
|
||||
}
|
||||
|
|
|
@ -128,7 +128,7 @@ fn commReadThread(gpa: std.mem.Allocator, r: anytype, w: anytype) void {
|
|||
|
||||
fn commWriteThread(gpa: std.mem.Allocator, w: anytype) !void {
|
||||
var sectimer = try time.Timer.start();
|
||||
var block_count: u64 = 801365;
|
||||
var block_count: u32 = 801365;
|
||||
|
||||
while (true) {
|
||||
time.sleep(time.ns_per_s);
|
||||
|
@ -164,6 +164,76 @@ fn commWriteThread(gpa: std.mem.Allocator, w: anytype) !void {
|
|||
},
|
||||
};
|
||||
comm.write(gpa, w, .{ .bitcoind_report = btcrep }) catch |err| logger.err("comm.write: {any}", .{err});
|
||||
|
||||
if (block_count % 2 == 0) {
|
||||
const lndrep: comm.Message.LightningReport = .{
|
||||
.version = "0.16.4-beta commit=v0.16.4-beta",
|
||||
.pubkey = "142874abcdeadbeef8839bdfaf8439fac9b0327bf78acdee8928efbac982de822a",
|
||||
.alias = "testnode",
|
||||
.npeers = 15,
|
||||
.height = block_count,
|
||||
.hash = "00000000000000000002bf8029f6be4e40b4a3e0e161b6a1044ddaf9eb126504",
|
||||
.sync = .{ .chain = true, .graph = true },
|
||||
.uris = &.{}, // TODO
|
||||
.totalbalance = .{ .local = 10123567, .remote = 4239870, .unsettled = 0, .pending = 430221 },
|
||||
.totalfees = .{ .day = 13, .week = 132, .month = 1321 },
|
||||
.channels = &.{
|
||||
.{
|
||||
.id = null,
|
||||
.state = .pending_open,
|
||||
.private = false,
|
||||
.point = "1b332afe982befbdcbadff33099743099eef00bcdbaef788320db328efeaa91b:0",
|
||||
.closetxid = null,
|
||||
.peer_pubkey = "def3829fbdeadbeef8839bdfaf8439fac9b0327bf78acdee8928efbac229aaabc2",
|
||||
.peer_alias = "chan-peer-alias1",
|
||||
.capacity = 900000,
|
||||
.balance = .{ .local = 1123456, .remote = 0, .unsettled = 0, .limbo = 0 },
|
||||
.totalsats = .{ .sent = 0, .received = 0 },
|
||||
.fees = .{ .base = 0, .ppm = 0 },
|
||||
},
|
||||
.{
|
||||
.id = null,
|
||||
.state = .pending_close,
|
||||
.private = false,
|
||||
.point = "932baef3982befbdcbadff33099743099eef00bcdbaef788320db328e82afdd7:0",
|
||||
.closetxid = "fe829832982befbdcbadff33099743099eef00bcdbaef788320db328eaffeb2b",
|
||||
.peer_pubkey = "01feba38fe8adbeef8839bdfaf8439fac9b0327bf78acdee8928efbac2abfec831",
|
||||
.peer_alias = "chan-peer-alias2",
|
||||
.capacity = 800000,
|
||||
.balance = .{ .local = 10000, .remote = 788000, .unsettled = 0, .limbo = 10000 },
|
||||
.totalsats = .{ .sent = 0, .received = 0 },
|
||||
.fees = .{ .base = 0, .ppm = 0 },
|
||||
},
|
||||
.{
|
||||
.id = "848352385882718209",
|
||||
.state = .active,
|
||||
.private = false,
|
||||
.point = "36277666abcbefbdcbadff33099743099eef00bcdbaef788320db328e828e00d:1",
|
||||
.closetxid = null,
|
||||
.peer_pubkey = "e7287abcfdeadbeef8839bdfaf8439fac9b0327bf78acdee8928efbac229acddbe",
|
||||
.peer_alias = "chan-peer-alias3",
|
||||
.capacity = 1000000,
|
||||
.balance = .{ .local = 1000000 / 2, .remote = 1000000 / 2, .unsettled = 0, .limbo = 0 },
|
||||
.totalsats = .{ .sent = 3287320, .received = 2187482 },
|
||||
.fees = .{ .base = 1000, .ppm = 400 },
|
||||
},
|
||||
.{
|
||||
.id = "134439885882718428",
|
||||
.state = .inactive,
|
||||
.private = false,
|
||||
.point = "abafe483982befbdcbadff33099743099eef00bcdbaef788320db328e828339c:0",
|
||||
.closetxid = null,
|
||||
.peer_pubkey = "20398287fdeadbeef8839bdfaf8439fac9b0327bf78acdee8928efbac229a03928",
|
||||
.peer_alias = "chan-peer-alias4",
|
||||
.capacity = 900000,
|
||||
.balance = .{ .local = 900000, .remote = 0, .unsettled = 0, .limbo = 0 },
|
||||
.totalsats = .{ .sent = 328732, .received = 2187482 },
|
||||
.fees = .{ .base = 1000, .ppm = 500 },
|
||||
},
|
||||
},
|
||||
};
|
||||
comm.write(gpa, w, .{ .lightning_report = lndrep }) catch |err| logger.err("comm.write: {any}", .{err});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,11 @@ int nm_create_info_panel(lv_obj_t *parent);
|
|||
*/
|
||||
int nm_create_bitcoin_panel(lv_obj_t *parent);
|
||||
|
||||
/**
|
||||
* creates the lightning tab panel.
|
||||
*/
|
||||
int nm_create_lightning_panel(lv_obj_t *parent);
|
||||
|
||||
/**
|
||||
* invoken when the UI is switched to the network settings tab.
|
||||
*/
|
||||
|
@ -108,15 +113,6 @@ static void textarea_event_cb(lv_event_t *e)
|
|||
}
|
||||
}
|
||||
|
||||
static void create_lnd_panel(lv_obj_t *parent)
|
||||
{
|
||||
lv_obj_t *label = lv_label_create(parent);
|
||||
lv_label_set_text_static(label,
|
||||
"lightning tab isn't designed yet\n"
|
||||
"follow https://nakamochi.io");
|
||||
lv_obj_center(label);
|
||||
}
|
||||
|
||||
static struct {
|
||||
lv_obj_t *wifi_spinner_obj; /* lv_spinner_create */
|
||||
lv_obj_t *wifi_status_obj; /* lv_label_create */
|
||||
|
@ -342,7 +338,9 @@ extern int nm_ui_init(lv_disp_t *disp)
|
|||
if (tab_lnd == NULL) {
|
||||
return -1;
|
||||
}
|
||||
create_lnd_panel(tab_lnd);
|
||||
if (nm_create_lightning_panel(tab_lnd) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
lv_obj_t *tab_settings = lv_tabview_add_tab(tabview, LV_SYMBOL_SETTINGS " SETTINGS");
|
||||
if (tab_settings == NULL) {
|
||||
|
|
|
@ -0,0 +1,198 @@
|
|||
//! lightning main tab panel and other functionality.
|
||||
//! all functions assume LVGL is init'ed and ui mutex is locked on entry.
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const comm = @import("../comm.zig");
|
||||
const lvgl = @import("lvgl.zig");
|
||||
const xfmt = @import("../xfmt.zig");
|
||||
|
||||
const logger = std.log.scoped(.ui_lnd);
|
||||
/// label color mark start to make "label:" part of a "label: value"
|
||||
/// in a different color.
|
||||
const cmark = "#bbbbbb ";
|
||||
|
||||
var tab: struct {
|
||||
info: struct {
|
||||
alias: lvgl.Label,
|
||||
blockhash: lvgl.Label,
|
||||
currblock: lvgl.Label,
|
||||
npeers: lvgl.Label,
|
||||
pubkey: lvgl.Label,
|
||||
version: lvgl.Label,
|
||||
},
|
||||
balance: struct {
|
||||
avail: lvgl.Bar, // local vs remote
|
||||
local: lvgl.Label,
|
||||
remote: lvgl.Label,
|
||||
unsettled: lvgl.Label,
|
||||
pending: lvgl.Label,
|
||||
fees: lvgl.Label, // day, week, month
|
||||
},
|
||||
channels_cont: lvgl.FlexLayout,
|
||||
} = undefined;
|
||||
|
||||
/// creates the tab content with all elements.
|
||||
/// must be called only once at UI init.
|
||||
pub fn initTabPanel(cont: lvgl.Container) !void {
|
||||
const parent = cont.flex(.column, .{});
|
||||
|
||||
// info section
|
||||
{
|
||||
const card = try lvgl.Card.new(parent, "INFO");
|
||||
const row = try lvgl.FlexLayout.new(card, .row, .{});
|
||||
row.setHeightToContent();
|
||||
row.setWidth(lvgl.sizePercent(100));
|
||||
row.clearFlag(.scrollable);
|
||||
// left column
|
||||
const left = try lvgl.FlexLayout.new(row, .column, .{});
|
||||
left.setHeightToContent();
|
||||
left.setWidth(lvgl.sizePercent(50));
|
||||
left.setPad(10, .row, .{});
|
||||
tab.info.alias = try lvgl.Label.new(left, "ALIAS\n", .{ .recolor = true });
|
||||
tab.info.pubkey = try lvgl.Label.new(left, "PUBKEY\n", .{ .recolor = true });
|
||||
tab.info.version = try lvgl.Label.new(left, "VERSION\n", .{ .recolor = true });
|
||||
// right column
|
||||
const right = try lvgl.FlexLayout.new(row, .column, .{});
|
||||
right.setHeightToContent();
|
||||
right.setWidth(lvgl.sizePercent(50));
|
||||
right.setPad(10, .row, .{});
|
||||
tab.info.currblock = try lvgl.Label.new(right, "HEIGHT\n", .{ .recolor = true });
|
||||
tab.info.blockhash = try lvgl.Label.new(right, "BLOCK HASH\n", .{ .recolor = true });
|
||||
tab.info.npeers = try lvgl.Label.new(right, "CONNECTED PEERS\n", .{ .recolor = true });
|
||||
}
|
||||
// balance section
|
||||
{
|
||||
const card = try lvgl.Card.new(parent, "BALANCE");
|
||||
const row = try lvgl.FlexLayout.new(card, .row, .{});
|
||||
row.setWidth(lvgl.sizePercent(100));
|
||||
row.clearFlag(.scrollable);
|
||||
// left column
|
||||
const left = try lvgl.FlexLayout.new(row, .column, .{});
|
||||
left.setWidth(lvgl.sizePercent(50));
|
||||
left.setPad(10, .row, .{});
|
||||
tab.balance.avail = try lvgl.Bar.new(left);
|
||||
tab.balance.avail.setWidth(lvgl.sizePercent(90));
|
||||
const subrow = try lvgl.FlexLayout.new(left, .row, .{ .main = .space_between });
|
||||
subrow.setWidth(lvgl.sizePercent(90));
|
||||
subrow.setHeightToContent();
|
||||
tab.balance.local = try lvgl.Label.new(subrow, "LOCAL\n", .{ .recolor = true });
|
||||
tab.balance.remote = try lvgl.Label.new(subrow, "REMOTE\n", .{ .recolor = true });
|
||||
// right column
|
||||
const right = try lvgl.FlexLayout.new(row, .column, .{});
|
||||
right.setWidth(lvgl.sizePercent(50));
|
||||
right.setPad(10, .row, .{});
|
||||
tab.balance.pending = try lvgl.Label.new(right, "PENDING\n", .{ .recolor = true });
|
||||
tab.balance.unsettled = try lvgl.Label.new(right, "UNSETTLED\n", .{ .recolor = true });
|
||||
// bottom
|
||||
tab.balance.fees = try lvgl.Label.new(card, "ACCUMULATED FORWARDING FEES\n", .{ .recolor = true });
|
||||
}
|
||||
// channels section
|
||||
{
|
||||
const card = try lvgl.Card.new(parent, "CHANNELS");
|
||||
tab.channels_cont = try lvgl.FlexLayout.new(card, .column, .{});
|
||||
tab.channels_cont.setHeightToContent();
|
||||
tab.channels_cont.setWidth(lvgl.sizePercent(100));
|
||||
tab.channels_cont.clearFlag(.scrollable);
|
||||
tab.channels_cont.setPad(10, .row, .{});
|
||||
}
|
||||
}
|
||||
|
||||
/// updates the tab with new data from the report.
|
||||
/// the tab must be inited first with initTabPanel.
|
||||
pub fn updateTabPanel(rep: comm.Message.LightningReport) !void {
|
||||
var buf: [512]u8 = undefined;
|
||||
|
||||
// info section
|
||||
try tab.info.alias.setTextFmt(&buf, cmark ++ "ALIAS#\n{s}", .{rep.alias});
|
||||
try tab.info.pubkey.setTextFmt(&buf, cmark ++ "PUBKEY#\n{s}\n{s}", .{ rep.pubkey[0..33], rep.pubkey[33..] });
|
||||
try tab.info.version.setTextFmt(&buf, cmark ++ "VERSION#\n{s}", .{rep.version});
|
||||
try tab.info.currblock.setTextFmt(&buf, cmark ++ "HEIGHT#\n{d}", .{rep.height});
|
||||
try tab.info.blockhash.setTextFmt(&buf, cmark ++ "BLOCK HASH#\n{s}\n{s}", .{ rep.hash[0..32], rep.hash[32..] });
|
||||
try tab.info.npeers.setTextFmt(&buf, cmark ++ "CONNECTED PEERS#\n{d}", .{rep.npeers});
|
||||
|
||||
// balance section
|
||||
const local_pct: i32 = pct: {
|
||||
const total = rep.totalbalance.local + rep.totalbalance.remote;
|
||||
if (total == 0) {
|
||||
break :pct 0;
|
||||
}
|
||||
const v = @as(f64, @floatFromInt(rep.totalbalance.local)) / @as(f64, @floatFromInt(total));
|
||||
break :pct @intFromFloat(v * 100);
|
||||
};
|
||||
tab.balance.avail.setValue(local_pct);
|
||||
try tab.balance.local.setTextFmt(&buf, cmark ++ "LOCAL#\n{} sat", .{xfmt.imetric(rep.totalbalance.local)});
|
||||
try tab.balance.remote.setTextFmt(&buf, cmark ++ "REMOTE#\n{} sat", .{xfmt.imetric(rep.totalbalance.remote)});
|
||||
try tab.balance.pending.setTextFmt(&buf, cmark ++ "PENDING#\n{} sat", .{xfmt.imetric(rep.totalbalance.pending)});
|
||||
try tab.balance.unsettled.setTextFmt(&buf, cmark ++ "UNSETTLED#\n{}", .{xfmt.imetric(rep.totalbalance.unsettled)});
|
||||
try tab.balance.fees.setTextFmt(&buf, cmark ++ "ACCUMULATED FORWARDING FEES#\nDAY: {} sat WEEK: {} sat MONTH: {} sat", .{
|
||||
xfmt.umetric(rep.totalfees.day),
|
||||
xfmt.umetric(rep.totalfees.week),
|
||||
xfmt.umetric(rep.totalfees.month),
|
||||
});
|
||||
|
||||
// channels section
|
||||
tab.channels_cont.deleteChildren();
|
||||
for (rep.channels) |ch| {
|
||||
const chbox = (try lvgl.Container.new(tab.channels_cont)).flex(.column, .{});
|
||||
chbox.setWidth(lvgl.sizePercent(100));
|
||||
chbox.setHeightToContent();
|
||||
_ = try switch (ch.state) {
|
||||
// TODO: sanitize peer_alias?
|
||||
.active => lvgl.Label.newFmt(chbox, &buf, "{s}", .{ch.peer_alias}, .{}),
|
||||
.inactive => lvgl.Label.newFmt(chbox, &buf, "#ff0000 [INACTIVE]# {s}", .{ch.peer_alias}, .{ .recolor = true }),
|
||||
.pending_open => lvgl.Label.new(chbox, "#00ff00 [PENDING OPEN]#", .{ .recolor = true }),
|
||||
.pending_close => lvgl.Label.new(chbox, "#ffff00 [PENDING CLOSE]#", .{ .recolor = true }),
|
||||
};
|
||||
const row = try lvgl.FlexLayout.new(chbox, .row, .{});
|
||||
row.setWidth(lvgl.sizePercent(100));
|
||||
row.clearFlag(.scrollable);
|
||||
row.setHeightToContent();
|
||||
|
||||
// left column
|
||||
const left = try lvgl.FlexLayout.new(row, .column, .{});
|
||||
left.setWidth(lvgl.sizePercent(46));
|
||||
left.setHeightToContent();
|
||||
left.setPad(10, .row, .{});
|
||||
const bbar = try lvgl.Bar.new(left);
|
||||
bbar.setWidth(lvgl.sizePercent(100));
|
||||
const chan_local_pct: i32 = pct: {
|
||||
const total = ch.balance.local + ch.balance.remote;
|
||||
if (total == 0) {
|
||||
break :pct 0;
|
||||
}
|
||||
const v = @as(f64, @floatFromInt(ch.balance.local)) / @as(f64, @floatFromInt(total));
|
||||
break :pct @intFromFloat(v * 100);
|
||||
};
|
||||
bbar.setValue(chan_local_pct);
|
||||
const subrow = try lvgl.FlexLayout.new(left, .row, .{ .main = .space_between });
|
||||
subrow.setWidth(lvgl.sizePercent(100));
|
||||
subrow.setHeightToContent();
|
||||
const subcol1 = try lvgl.FlexLayout.new(subrow, .column, .{});
|
||||
subcol1.setPad(10, .row, .{});
|
||||
subcol1.setHeightToContent();
|
||||
const subcol2 = try lvgl.FlexLayout.new(subrow, .column, .{});
|
||||
subcol2.setPad(10, .row, .{});
|
||||
_ = try lvgl.Label.newFmt(subcol1, &buf, cmark ++ "LOCAL#\n{} sat", .{xfmt.imetric(ch.balance.local)}, .{ .recolor = true });
|
||||
_ = try lvgl.Label.newFmt(subcol1, &buf, cmark ++ "RECEIVED#\n{} sat", .{xfmt.imetric(ch.totalsats.received)}, .{ .recolor = true });
|
||||
if (ch.state == .active or ch.state == .inactive) {
|
||||
_ = try lvgl.Label.newFmt(subcol1, &buf, cmark ++ "BASE FEE#\n{} msat", .{xfmt.imetric(ch.fees.base)}, .{ .recolor = true });
|
||||
_ = try lvgl.Label.newFmt(subcol1, &buf, cmark ++ "FEE PPM#\n{d}", .{ch.fees.ppm}, .{ .recolor = true });
|
||||
}
|
||||
_ = try lvgl.Label.newFmt(subcol2, &buf, cmark ++ "REMOTE#\n{} sat", .{xfmt.imetric(ch.balance.remote)}, .{ .recolor = true });
|
||||
_ = try lvgl.Label.newFmt(subcol2, &buf, cmark ++ "SENT#\n{} sat", .{xfmt.imetric(ch.totalsats.sent)}, .{ .recolor = true });
|
||||
|
||||
// right column
|
||||
const right = try lvgl.FlexLayout.new(row, .column, .{});
|
||||
right.setWidth(lvgl.sizePercent(54));
|
||||
right.setHeightToContent();
|
||||
right.setPad(10, .row, .{});
|
||||
if (ch.id) |id| {
|
||||
_ = try lvgl.Label.newFmt(right, &buf, cmark ++ "ID#\n{s}", .{id}, .{ .recolor = true });
|
||||
}
|
||||
_ = try lvgl.Label.newFmt(right, &buf, cmark ++ "FUNDING TX#\n{s}\n{s}", .{ ch.point[0..32], ch.point[32..] }, .{ .recolor = true });
|
||||
if (ch.closetxid) |tx| {
|
||||
_ = try lvgl.Label.newFmt(right, &buf, cmark ++ "CLOSING TX#\n{s}\n{s}", .{ tx[0..32], tx[32..] }, .{ .recolor = true });
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ const symbol = @import("symbol.zig");
|
|||
const widget = @import("widget.zig");
|
||||
pub const poweroff = @import("poweroff.zig");
|
||||
pub const bitcoin = @import("bitcoin.zig");
|
||||
pub const lightning = @import("lightning.zig");
|
||||
|
||||
const logger = std.log.scoped(.ui);
|
||||
|
||||
|
@ -43,6 +44,14 @@ export fn nm_create_bitcoin_panel(parent: *lvgl.LvObj) c_int {
|
|||
return 0;
|
||||
}
|
||||
|
||||
export fn nm_create_lightning_panel(parent: *lvgl.LvObj) c_int {
|
||||
lightning.initTabPanel(lvgl.Container{ .lvobj = parent }) catch |err| {
|
||||
logger.err("createLightningPanel: {any}", .{err});
|
||||
return -1;
|
||||
};
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn createInfoPanel(cont: lvgl.Container) !void {
|
||||
const flex = cont.flex(.column, .{});
|
||||
var buf: [100]u8 = undefined;
|
||||
|
|
40
src/xfmt.zig
40
src/xfmt.zig
|
@ -8,6 +8,16 @@ pub fn unix(sec: u64) std.fmt.Formatter(formatUnix) {
|
|||
return .{ .data = sec };
|
||||
}
|
||||
|
||||
/// returns a metric formatter, outputting the value with SI unit suffix.
|
||||
pub fn imetric(val: i64) std.fmt.Formatter(formatMetricI) {
|
||||
return .{ .data = val };
|
||||
}
|
||||
|
||||
/// returns a metric formatter, outputting the value with SI unit suffix.
|
||||
pub fn umetric(val: u64) std.fmt.Formatter(formatMetricU) {
|
||||
return .{ .data = val };
|
||||
}
|
||||
|
||||
fn formatUnix(sec: u64, comptime fmt: []const u8, opts: std.fmt.FormatOptions, w: anytype) !void {
|
||||
_ = fmt; // unused
|
||||
_ = opts;
|
||||
|
@ -29,3 +39,33 @@ fn formatUnix(sec: u64, comptime fmt: []const u8, opts: std.fmt.FormatOptions, w
|
|||
daysec.getSecondsIntoMinute(),
|
||||
});
|
||||
}
|
||||
|
||||
fn formatMetricI(value: i64, comptime fmt: []const u8, opts: std.fmt.FormatOptions, w: anytype) !void {
|
||||
const uval: u64 = std.math.absCast(value);
|
||||
const base: u64 = 1000;
|
||||
if (uval < base) {
|
||||
return std.fmt.formatIntValue(value, fmt, opts, w);
|
||||
}
|
||||
|
||||
if (value < 0) {
|
||||
try w.writeByte('-');
|
||||
}
|
||||
return formatMetricU(uval, fmt, opts, w);
|
||||
}
|
||||
|
||||
/// based on `std.fmt.fmtIntSizeDec`.
|
||||
fn formatMetricU(value: u64, comptime fmt: []const u8, opts: std.fmt.FormatOptions, w: anytype) !void {
|
||||
const lossyCast = std.math.lossyCast;
|
||||
const base: u64 = 1000;
|
||||
if (value < base) {
|
||||
return std.fmt.formatIntValue(value, fmt, opts, w);
|
||||
}
|
||||
|
||||
const mags_si = " kMGTPEZY";
|
||||
const log2 = std.math.log2(value);
|
||||
const m = @min(log2 / comptime std.math.log2(base), mags_si.len - 1);
|
||||
const newval = lossyCast(f64, value) / std.math.pow(f64, lossyCast(f64, base), lossyCast(f64, m));
|
||||
const suffix = mags_si[m];
|
||||
try std.fmt.formatFloatDecimal(newval, opts, w);
|
||||
try w.writeByte(suffix);
|
||||
}
|
||||
|
|
Reference in New Issue