Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 26 additions & 14 deletions src/app.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,54 +2,66 @@ const std = @import("std");

const Loop = @import("jsruntime").Loop;
const Allocator = std.mem.Allocator;
const HttpClient = @import("http/Client.zig");
const Telemetry = @import("telemetry/telemetry.zig").Telemetry;

const log = std.log.scoped(.app);

pub const RunMode = enum {
serve,
fetch,
};

// Container for global state / objects that various parts of the system
// might need.
pub const App = struct {
loop: *Loop,
app_dir_path: ?[]const u8,
allocator: Allocator,
telemetry: Telemetry,
http_client: HttpClient,
app_dir_path: ?[]const u8,

pub const RunMode = enum {
serve,
fetch,
};

pub fn init(allocator: Allocator, run_mode: RunMode) !*App {
const app = try allocator.create(App);
errdefer allocator.destroy(app);

pub fn init(allocator: Allocator, run_mode: RunMode) !App {
const loop = try allocator.create(Loop);
errdefer allocator.destroy(loop);

loop.* = try Loop.init(allocator);
errdefer loop.deinit();

const app_dir_path = getAndMakeAppDir(allocator);
const telemetry = Telemetry.init(allocator, run_mode, app_dir_path);
errdefer telemetry.deinit();

return .{
app.* = .{
.loop = loop,
.allocator = allocator,
.telemetry = telemetry,
.telemetry = undefined,
.app_dir_path = app_dir_path,
.http_client = .{ .allocator = allocator },
};
app.telemetry = Telemetry.init(app, run_mode);

return app;
}

pub fn deinit(self: *App) void {
const allocator = self.allocator;
if (self.app_dir_path) |app_dir_path| {
self.allocator.free(app_dir_path);
allocator.free(app_dir_path);
}

self.telemetry.deinit();
self.loop.deinit();
self.allocator.destroy(self.loop);
allocator.destroy(self.loop);
self.http_client.deinit();
allocator.destroy(self);
}
};

fn getAndMakeAppDir(allocator: Allocator) ?[]const u8 {
if (@import("builtin").is_test) {
return allocator.dupe(u8, "/tmp") catch unreachable;
}
const app_dir_path = std.fs.getAppDataDir(allocator, "lightpanda") catch |err| {
log.warn("failed to get lightpanda data dir: {}", .{err});
return null;
Expand Down
7 changes: 3 additions & 4 deletions src/browser/browser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ pub const Browser = struct {
app: *App,
session: ?*Session,
allocator: Allocator,
http_client: HttpClient,
http_client: *HttpClient,
session_pool: SessionPool,
page_arena: std.heap.ArenaAllocator,

Expand All @@ -75,15 +75,14 @@ pub const Browser = struct {
.app = app,
.session = null,
.allocator = allocator,
.http_client = .{ .allocator = allocator },
.http_client = @ptrCast(&app.http_client),
.session_pool = SessionPool.init(allocator),
.page_arena = std.heap.ArenaAllocator.init(allocator),
};
}

pub fn deinit(self: *Browser) void {
self.closeSession();
self.http_client.deinit();
self.session_pool.deinit();
self.page_arena.deinit();
}
Expand Down Expand Up @@ -454,7 +453,7 @@ pub const Page = struct {
// replace the user context document with the new one.
try session.env.setUserContext(.{
.document = html_doc,
.httpClient = &self.session.browser.http_client,
.httpClient = self.session.browser.http_client,
});

// browse the DOM tree to retrieve scripts
Expand Down
4 changes: 2 additions & 2 deletions src/cdp/testing.zig
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ const TestCDP = main.CDPT(struct {
});

const TestContext = struct {
app: App,
app: *App,
client: ?Client = null,
cdp_: ?TestCDP = null,
arena: std.heap.ArenaAllocator,
Expand All @@ -136,7 +136,7 @@ const TestContext = struct {
self.client = Client.init(self.arena.allocator());
// Don't use the arena here. We want to detect leaks in CDP.
// The arena is only for test-specific stuff
self.cdp_ = TestCDP.init(&self.app, &self.client.?);
self.cdp_ = TestCDP.init(self.app, &self.client.?);
}
return &self.cdp_.?;
}
Expand Down
4 changes: 2 additions & 2 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ pub fn main() !void {
app.telemetry.record(.{ .run = {} });

const timeout = std.time.ns_per_s * @as(u64, opts.timeout);
server.run(&app, address, timeout) catch |err| {
server.run(app, address, timeout) catch |err| {
log.err("Server error", .{});
return err;
};
Expand All @@ -92,7 +92,7 @@ pub fn main() !void {
defer vm.deinit();

// browser
var browser = Browser.init(&app);
var browser = Browser.init(app);
defer browser.deinit();

var session = try browser.newSession({});
Expand Down
22 changes: 11 additions & 11 deletions src/telemetry/lightpanda.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ const build_info = @import("build_info");
const Thread = std.Thread;
const Allocator = std.mem.Allocator;

const App = @import("../app.zig").App;
const telemetry = @import("telemetry.zig");
const RunMode = @import("../app.zig").RunMode;

const log = std.log.scoped(.telemetry);
const URL = "https://telemetry.lightpanda.io";
Expand All @@ -19,18 +19,21 @@ pub const LightPanda = struct {
allocator: Allocator,
mutex: std.Thread.Mutex,
cond: Thread.Condition,
client: *std.http.Client,
node_pool: std.heap.MemoryPool(List.Node),

const List = std.DoublyLinkedList(LightPandaEvent);

pub fn init(allocator: Allocator) !LightPanda {
pub fn init(app: *App) !LightPanda {
const allocator = app.allocator;
return .{
.cond = .{},
.mutex = .{},
.pending = .{},
.thread = null,
.running = true,
.allocator = allocator,
.client = @ptrCast(&app.http_client),
.uri = std.Uri.parse(URL) catch unreachable,
.node_pool = std.heap.MemoryPool(List.Node).init(allocator),
};
Expand All @@ -47,7 +50,7 @@ pub const LightPanda = struct {
self.node_pool.deinit();
}

pub fn send(self: *LightPanda, iid: ?[]const u8, run_mode: RunMode, raw_event: telemetry.Event) !void {
pub fn send(self: *LightPanda, iid: ?[]const u8, run_mode: App.RunMode, raw_event: telemetry.Event) !void {
const event = LightPandaEvent{
.iid = iid,
.mode = run_mode,
Expand All @@ -57,7 +60,7 @@ pub const LightPanda = struct {
self.mutex.lock();
defer self.mutex.unlock();
if (self.thread == null) {
self.thread = try std.Thread.spawn(.{}, run, .{self});
self.thread = try std.Thread.spawn(.{ .stack_size = 1024 * 1024 * 4 }, run, .{self});
}

const node = try self.node_pool.create();
Expand All @@ -68,19 +71,16 @@ pub const LightPanda = struct {
}

fn run(self: *LightPanda) void {
const client = self.client;
var arr: std.ArrayListUnmanaged(u8) = .{};
var client = std.http.Client{ .allocator = self.allocator };

defer {
arr.deinit(self.allocator);
client.deinit();
}
defer arr.deinit(self.allocator);

self.mutex.lock();
while (true) {
while (self.pending.popFirst()) |node| {
self.mutex.unlock();
self.postEvent(&node.data, &client, &arr) catch |err| {
self.postEvent(&node.data, client, &arr) catch |err| {
log.warn("Telementry reporting error: {}", .{err});
};
self.mutex.lock();
Expand Down Expand Up @@ -113,7 +113,7 @@ pub const LightPanda = struct {

const LightPandaEvent = struct {
iid: ?[]const u8,
mode: RunMode,
mode: App.RunMode,
event: telemetry.Event,

pub fn jsonStringify(self: *const LightPandaEvent, writer: anytype) !void {
Expand Down
39 changes: 21 additions & 18 deletions src/telemetry/telemetry.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ const builtin = @import("builtin");

const Allocator = std.mem.Allocator;

const App = @import("../app.zig").App;
const Loop = @import("jsruntime").Loop;
const uuidv4 = @import("../id.zig").uuidv4;
const RunMode = @import("../app.zig").RunMode;

const log = std.log.scoped(.telemetry);
const IID_FILE = "iid";
Expand All @@ -25,11 +25,11 @@ fn TelemetryT(comptime P: type) type {

disabled: bool,

run_mode: RunMode,
run_mode: App.RunMode,

const Self = @This();

pub fn init(allocator: Allocator, run_mode: RunMode, app_dir_path: ?[]const u8) Self {
pub fn init(app: *App, run_mode: App.RunMode) Self {
const disabled = std.process.hasEnvVarConstant("LIGHTPANDA_DISABLE_TELEMETRY");
if (builtin.mode != .Debug and builtin.is_test == false) {
log.info("telemetry {s}", .{if (disabled) "disabled" else "enabled"});
Expand All @@ -38,8 +38,8 @@ fn TelemetryT(comptime P: type) type {
return .{
.disabled = disabled,
.run_mode = run_mode,
.provider = try P.init(allocator),
.iid = if (disabled) null else getOrCreateId(app_dir_path),
.provider = try P.init(app),
.iid = if (disabled) null else getOrCreateId(app.app_dir_path),
};
}

Expand Down Expand Up @@ -104,32 +104,32 @@ pub const Event = union(enum) {
};

const NoopProvider = struct {
fn init(_: Allocator) !NoopProvider {
fn init(_: *App) !NoopProvider {
return .{};
}
fn deinit(_: NoopProvider) void {}
pub fn send(_: NoopProvider, _: ?[]const u8, _: RunMode, _: Event) !void {}
pub fn send(_: NoopProvider, _: ?[]const u8, _: App.RunMode, _: Event) !void {}
};

extern fn setenv(name: [*:0]u8, value: [*:0]u8, override: c_int) c_int;
extern fn unsetenv(name: [*:0]u8) c_int;

const testing = std.testing;
const testing = @import("../testing.zig");
test "telemetry: disabled by environment" {
_ = setenv(@constCast("LIGHTPANDA_DISABLE_TELEMETRY"), @constCast(""), 0);
defer _ = unsetenv(@constCast("LIGHTPANDA_DISABLE_TELEMETRY"));

const FailingProvider = struct {
fn init(_: Allocator) !@This() {
fn init(_: *App) !@This() {
return .{};
}
fn deinit(_: @This()) void {}
pub fn send(_: @This(), _: ?[]const u8, _: RunMode, _: Event) !void {
pub fn send(_: @This(), _: ?[]const u8, _: App.RunMode, _: Event) !void {
unreachable;
}
};

var telemetry = TelemetryT(FailingProvider).init(testing.allocator, .serve, null);
var telemetry = TelemetryT(FailingProvider).init(undefined, .serve);
defer telemetry.deinit();
telemetry.record(.{ .run = {} });
}
Expand All @@ -141,15 +141,18 @@ test "telemetry: getOrCreateId" {

const id1 = getOrCreateId("/tmp/").?;
const id2 = getOrCreateId("/tmp/").?;
try testing.expectEqualStrings(&id1, &id2);
try testing.expectEqual(&id1, &id2);

std.fs.cwd().deleteFile("/tmp/" ++ IID_FILE) catch {};
const id3 = getOrCreateId("/tmp/").?;
try testing.expectEqual(false, std.mem.eql(u8, &id1, &id3));
}

test "telemetry: sends event to provider" {
var telemetry = TelemetryT(MockProvider).init(testing.allocator, .serve, "/tmp/");
var app = testing.app(.{});
defer app.deinit();

var telemetry = TelemetryT(MockProvider).init(app, .serve);
defer telemetry.deinit();
const mock = &telemetry.provider;

Expand All @@ -165,28 +168,28 @@ test "telemetry: sends event to provider" {

const MockProvider = struct {
iid: ?[]const u8,
run_mode: ?RunMode,
run_mode: ?App.RunMode,
allocator: Allocator,
events: std.ArrayListUnmanaged(Event),

fn init(allocator: Allocator) !@This() {
fn init(app: *App) !@This() {
return .{
.iid = null,
.run_mode = null,
.events = .{},
.allocator = allocator,
.allocator = app.allocator,
};
}
fn deinit(self: *MockProvider) void {
self.events.deinit(self.allocator);
}
pub fn send(self: *MockProvider, iid: ?[]const u8, run_mode: RunMode, events: Event) !void {
pub fn send(self: *MockProvider, iid: ?[]const u8, run_mode: App.RunMode, events: Event) !void {
if (self.iid == null) {
try testing.expectEqual(null, self.run_mode);
self.iid = iid.?;
self.run_mode = run_mode;
} else {
try testing.expectEqualStrings(self.iid.?, iid.?);
try testing.expectEqual(self.iid.?, iid.?);
try testing.expectEqual(self.run_mode.?, run_mode);
}
try self.events.append(self.allocator, events);
Expand Down
Loading