Add a logging allocator that uses std.log (#8511)

This commit is contained in:
Lee Cannon 2021-06-09 19:42:07 +01:00 committed by GitHub
parent 96c60bcca5
commit 629e2e7844
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 232 additions and 51 deletions

View File

@ -16,6 +16,9 @@ const maxInt = std.math.maxInt;
pub const LoggingAllocator = @import("heap/logging_allocator.zig").LoggingAllocator;
pub const loggingAllocator = @import("heap/logging_allocator.zig").loggingAllocator;
pub const ScopedLoggingAllocator = @import("heap/logging_allocator.zig").ScopedLoggingAllocator;
pub const LogToWriterAllocator = @import("heap/log_to_writer_allocator.zig").LogToWriterAllocator;
pub const logToWriterAllocator = @import("heap/log_to_writer_allocator.zig").logToWriterAllocator;
pub const ArenaAllocator = @import("heap/arena_allocator.zig").ArenaAllocator;
pub const GeneralPurposeAllocator = @import("heap/general_purpose_allocator.zig").GeneralPurposeAllocator;
@ -1162,4 +1165,5 @@ pub fn testAllocatorAlignedShrink(base_allocator: *mem.Allocator) !void {
test "heap" {
_ = @import("heap/logging_allocator.zig");
_ = @import("heap/log_to_writer_allocator.zig");
}

View File

@ -0,0 +1,108 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2015-2021 Zig Contributors
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
// The MIT license requires this copyright notice to be included in all copies
// and substantial portions of the software.
const std = @import("../std.zig");
const Allocator = std.mem.Allocator;
/// This allocator is used in front of another allocator and logs to the provided writer
/// on every call to the allocator. Writer errors are ignored.
pub fn LogToWriterAllocator(comptime Writer: type) type {
return struct {
allocator: Allocator,
parent_allocator: *Allocator,
writer: Writer,
const Self = @This();
pub fn init(parent_allocator: *Allocator, writer: Writer) Self {
return Self{
.allocator = Allocator{
.allocFn = alloc,
.resizeFn = resize,
},
.parent_allocator = parent_allocator,
.writer = writer,
};
}
fn alloc(
allocator: *Allocator,
len: usize,
ptr_align: u29,
len_align: u29,
ra: usize,
) error{OutOfMemory}![]u8 {
const self = @fieldParentPtr(Self, "allocator", allocator);
self.writer.print("alloc : {}", .{len}) catch {};
const result = self.parent_allocator.allocFn(self.parent_allocator, len, ptr_align, len_align, ra);
if (result) |buff| {
self.writer.print(" success!\n", .{}) catch {};
} else |err| {
self.writer.print(" failure!\n", .{}) catch {};
}
return result;
}
fn resize(
allocator: *Allocator,
buf: []u8,
buf_align: u29,
new_len: usize,
len_align: u29,
ra: usize,
) error{OutOfMemory}!usize {
const self = @fieldParentPtr(Self, "allocator", allocator);
if (new_len == 0) {
self.writer.print("free : {}\n", .{buf.len}) catch {};
} else if (new_len <= buf.len) {
self.writer.print("shrink: {} to {}\n", .{ buf.len, new_len }) catch {};
} else {
self.writer.print("expand: {} to {}", .{ buf.len, new_len }) catch {};
}
if (self.parent_allocator.resizeFn(self.parent_allocator, buf, buf_align, new_len, len_align, ra)) |resized_len| {
if (new_len > buf.len) {
self.writer.print(" success!\n", .{}) catch {};
}
return resized_len;
} else |e| {
std.debug.assert(new_len > buf.len);
self.writer.print(" failure!\n", .{}) catch {};
return e;
}
}
};
}
/// This allocator is used in front of another allocator and logs to the provided writer
/// on every call to the allocator. Writer errors are ignored.
pub fn logToWriterAllocator(
parent_allocator: *Allocator,
writer: anytype,
) LogToWriterAllocator(@TypeOf(writer)) {
return LogToWriterAllocator(@TypeOf(writer)).init(parent_allocator, writer);
}
test "LogToWriterAllocator" {
var log_buf: [255]u8 = undefined;
var fbs = std.io.fixedBufferStream(&log_buf);
var allocator_buf: [10]u8 = undefined;
var fixedBufferAllocator = std.mem.validationWrap(std.heap.FixedBufferAllocator.init(&allocator_buf));
const allocator = &logToWriterAllocator(&fixedBufferAllocator.allocator, fbs.writer()).allocator;
var a = try allocator.alloc(u8, 10);
a = allocator.shrink(a, 5);
try std.testing.expect(a.len == 5);
try std.testing.expectError(error.OutOfMemory, allocator.resize(a, 20));
allocator.free(a);
try std.testing.expectEqualSlices(u8,
\\alloc : 10 success!
\\shrink: 10 to 5
\\expand: 5 to 20 failure!
\\free : 5
\\
, fbs.getWritten());
}

View File

@ -6,28 +6,56 @@
const std = @import("../std.zig");
const Allocator = std.mem.Allocator;
/// This allocator is used in front of another allocator and logs to the provided stream
/// on every call to the allocator. Stream errors are ignored.
/// If https://github.com/ziglang/zig/issues/2586 is implemented, this API can be improved.
pub fn LoggingAllocator(comptime Writer: type) type {
/// This allocator is used in front of another allocator and logs to `std.log`
/// on every call to the allocator.
/// For logging to a `std.io.Writer` see `std.heap.LogToWriterAllocator`
pub fn LoggingAllocator(
comptime success_log_level: std.log.Level,
comptime failure_log_level: std.log.Level,
) type {
return ScopedLoggingAllocator(.default, success_log_level, failure_log_level);
}
/// This allocator is used in front of another allocator and logs to `std.log`
/// with the given scope on every call to the allocator.
/// For logging to a `std.io.Writer` see `std.heap.LogToWriterAllocator`
pub fn ScopedLoggingAllocator(
comptime scope: @Type(.EnumLiteral),
comptime success_log_level: std.log.Level,
comptime failure_log_level: std.log.Level,
) type {
const log = std.log.scoped(scope);
return struct {
allocator: Allocator,
parent_allocator: *Allocator,
writer: Writer,
const Self = @This();
pub fn init(parent_allocator: *Allocator, writer: Writer) Self {
return Self{
pub fn init(parent_allocator: *Allocator) Self {
return .{
.allocator = Allocator{
.allocFn = alloc,
.resizeFn = resize,
},
.parent_allocator = parent_allocator,
.writer = writer,
};
}
// This function is required as the `std.log.log` function is not public
fn logHelper(comptime log_level: std.log.Level, comptime format: []const u8, args: anytype) callconv(.Inline) void {
switch (log_level) {
.emerg => log.emerg(format, args),
.alert => log.alert(format, args),
.crit => log.crit(format, args),
.err => log.err(format, args),
.warn => log.warn(format, args),
.notice => log.notice(format, args),
.info => log.info(format, args),
.debug => log.debug(format, args),
}
}
fn alloc(
allocator: *Allocator,
len: usize,
@ -36,12 +64,19 @@ pub fn LoggingAllocator(comptime Writer: type) type {
ra: usize,
) error{OutOfMemory}![]u8 {
const self = @fieldParentPtr(Self, "allocator", allocator);
self.writer.print("alloc : {}", .{len}) catch {};
const result = self.parent_allocator.allocFn(self.parent_allocator, len, ptr_align, len_align, ra);
if (result) |buff| {
self.writer.print(" success!\n", .{}) catch {};
logHelper(
success_log_level,
"alloc - success - len: {}, ptr_align: {}, len_align: {}",
.{ len, ptr_align, len_align },
);
} else |err| {
self.writer.print(" failure!\n", .{}) catch {};
logHelper(
failure_log_level,
"alloc - failure: {s} - len: {}, ptr_align: {}, len_align: {}",
.{ @errorName(err), len, ptr_align, len_align },
);
}
return result;
}
@ -55,53 +90,41 @@ pub fn LoggingAllocator(comptime Writer: type) type {
ra: usize,
) error{OutOfMemory}!usize {
const self = @fieldParentPtr(Self, "allocator", allocator);
if (new_len == 0) {
self.writer.print("free : {}\n", .{buf.len}) catch {};
} else if (new_len <= buf.len) {
self.writer.print("shrink: {} to {}\n", .{ buf.len, new_len }) catch {};
} else {
self.writer.print("expand: {} to {}", .{ buf.len, new_len }) catch {};
}
if (self.parent_allocator.resizeFn(self.parent_allocator, buf, buf_align, new_len, len_align, ra)) |resized_len| {
if (new_len > buf.len) {
self.writer.print(" success!\n", .{}) catch {};
if (new_len == 0) {
logHelper(success_log_level, "free - success - len: {}", .{buf.len});
} else if (new_len <= buf.len) {
logHelper(
success_log_level,
"shrink - success - {} to {}, len_align: {}, buf_align: {}",
.{ buf.len, new_len, len_align, buf_align },
);
} else {
logHelper(
success_log_level,
"expand - success - {} to {}, len_align: {}, buf_align: {}",
.{ buf.len, new_len, len_align, buf_align },
);
}
return resized_len;
} else |e| {
} else |err| {
std.debug.assert(new_len > buf.len);
self.writer.print(" failure!\n", .{}) catch {};
return e;
logHelper(
failure_log_level,
"expand - failure: {s} - {} to {}, len_align: {}, buf_align: {}",
.{ @errorName(err), buf.len, new_len, len_align, buf_align },
);
return err;
}
}
};
}
pub fn loggingAllocator(
parent_allocator: *Allocator,
writer: anytype,
) LoggingAllocator(@TypeOf(writer)) {
return LoggingAllocator(@TypeOf(writer)).init(parent_allocator, writer);
}
test "LoggingAllocator" {
var log_buf: [255]u8 = undefined;
var fbs = std.io.fixedBufferStream(&log_buf);
var allocator_buf: [10]u8 = undefined;
var fixedBufferAllocator = std.mem.validationWrap(std.heap.FixedBufferAllocator.init(&allocator_buf));
const allocator = &loggingAllocator(&fixedBufferAllocator.allocator, fbs.writer()).allocator;
var a = try allocator.alloc(u8, 10);
a = allocator.shrink(a, 5);
try std.testing.expect(a.len == 5);
try std.testing.expectError(error.OutOfMemory, allocator.resize(a, 20));
allocator.free(a);
try std.testing.expectEqualSlices(u8,
\\alloc : 10 success!
\\shrink: 10 to 5
\\expand: 5 to 20 failure!
\\free : 5
\\
, fbs.getWritten());
/// This allocator is used in front of another allocator and logs to `std.log`
/// on every call to the allocator.
/// For logging to a `std.io.Writer` see `std.heap.LogToWriterAllocator`
pub fn loggingAllocator(parent_allocator: *Allocator) LoggingAllocator(.debug, .crit) {
return LoggingAllocator(.debug, .crit).init(parent_allocator);
}

View File

@ -599,4 +599,50 @@ pub fn addCases(cases: *tests.CompareOutputContext) void {
\\emergency(c):
\\
);
// It is required to override the log function in order to print to stdout instead of stderr
cases.add("std.heap.LoggingAllocator logs to std.log",
\\const std = @import("std");
\\
\\pub const log_level: std.log.Level = .debug;
\\
\\pub fn main() !void {
\\ var allocator_buf: [10]u8 = undefined;
\\ var fixedBufferAllocator = std.mem.validationWrap(std.heap.FixedBufferAllocator.init(&allocator_buf));
\\ const allocator = &std.heap.loggingAllocator(&fixedBufferAllocator.allocator).allocator;
\\
\\ var a = try allocator.alloc(u8, 10);
\\ a = allocator.shrink(a, 5);
\\ try std.testing.expect(a.len == 5);
\\ try std.testing.expectError(error.OutOfMemory, allocator.resize(a, 20));
\\ allocator.free(a);
\\}
\\
\\pub fn log(
\\ comptime level: std.log.Level,
\\ comptime scope: @TypeOf(.EnumLiteral),
\\ comptime format: []const u8,
\\ args: anytype,
\\) void {
\\ const level_txt = switch (level) {
\\ .emerg => "emergency",
\\ .alert => "alert",
\\ .crit => "critical",
\\ .err => "error",
\\ .warn => "warning",
\\ .notice => "notice",
\\ .info => "info",
\\ .debug => "debug",
\\ };
\\ const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
\\ const stdout = std.io.getStdOut().writer();
\\ nosuspend stdout.print(level_txt ++ prefix2 ++ format ++ "\n", args) catch return;
\\}
,
\\debug: alloc - success - len: 10, ptr_align: 1, len_align: 0
\\debug: shrink - success - 10 to 5, len_align: 0, buf_align: 1
\\critical: expand - failure: OutOfMemory - 5 to 20, len_align: 0, buf_align: 1
\\debug: free - success - len: 5
\\
);
}