Add spall profiler and stop generating layout every frame
This commit is contained in:
@@ -204,3 +204,25 @@ FileIter IterateFiles(Allocator alo, String path) {
|
||||
Advance(&it);
|
||||
return it;
|
||||
}
|
||||
|
||||
#if _WIN32
|
||||
#include <Windows.h>
|
||||
double get_time_in_micros(void) {
|
||||
static double invfreq;
|
||||
if (!invfreq) {
|
||||
LARGE_INTEGER frequency;
|
||||
QueryPerformanceFrequency(&frequency);
|
||||
invfreq = 1000000.0 / frequency.QuadPart;
|
||||
}
|
||||
LARGE_INTEGER counter;
|
||||
QueryPerformanceCounter(&counter);
|
||||
return counter.QuadPart * invfreq;
|
||||
}
|
||||
#else
|
||||
#include <unistd.h>
|
||||
double get_time_in_micros(void) {
|
||||
struct timespec spec;
|
||||
clock_gettime(CLOCK_MONOTONIC, &spec);
|
||||
return (((double)spec.tv_sec) * 1000000) + (((double)spec.tv_nsec) / 1000);
|
||||
}
|
||||
#endif
|
||||
@@ -56,6 +56,7 @@ struct Window {
|
||||
Buffer buffer;
|
||||
History history;
|
||||
|
||||
bool not_regen_layout;
|
||||
Array<ColoredString> colored_strings;
|
||||
Arena layout_arena;
|
||||
Layout layout;
|
||||
@@ -68,6 +69,7 @@ struct Tuple {
|
||||
};
|
||||
|
||||
void DrawString(Font font, String text, Vector2 position, float fontSize, float spacing, Color tint) {
|
||||
ProfileFunction();
|
||||
if (font.texture.id == 0) font = GetFontDefault(); // Security check in case of not valid font
|
||||
|
||||
float textOffsetX = 0.0f; // Offset X to next character to draw
|
||||
@@ -92,6 +94,7 @@ void DrawString(Font font, String text, Vector2 position, float fontSize, float
|
||||
}
|
||||
|
||||
Vector2 MeasureString(Font font, String text, float fontSize, float spacing) {
|
||||
ProfileFunction();
|
||||
Vector2 textSize = {0};
|
||||
if ((font.texture.id == 0) || (text.data == NULL)) return textSize; // Security check
|
||||
|
||||
@@ -132,6 +135,7 @@ Vector2 MeasureString(Font font, String text, float fontSize, float spacing) {
|
||||
}
|
||||
|
||||
void RegenLayout(Window *window) {
|
||||
ProfileFunction();
|
||||
Clear(&window->layout_arena);
|
||||
window->layout = {};
|
||||
|
||||
@@ -228,15 +232,20 @@ void RegenLayout(Window *window) {
|
||||
}
|
||||
|
||||
layout.buffer_world_pixel_size.y = text_offset_y;
|
||||
window->not_regen_layout = !window->not_regen_layout;
|
||||
}
|
||||
|
||||
// @todo: this is not taking line wrapping into account
|
||||
int64_t YPosWorldToLineNumber(Window &window, float ypos_window_buffer_world_units) {
|
||||
ProfileFunction();
|
||||
float line_spacing = window.font_size;
|
||||
int64_t line = (int64_t)floorf(ypos_window_buffer_world_units / line_spacing);
|
||||
return line;
|
||||
}
|
||||
|
||||
// @todo: this is not taking line wrapping into account
|
||||
LayoutRow *GetLayoutRow(Window &window, float ypos_window_buffer_world_units) {
|
||||
ProfileFunction();
|
||||
int64_t line = YPosWorldToLineNumber(window, ypos_window_buffer_world_units);
|
||||
if (line >= 0 && line < window.layout.rows.len) {
|
||||
return window.layout.rows.data + line;
|
||||
@@ -245,6 +254,7 @@ LayoutRow *GetLayoutRow(Window &window, float ypos_window_buffer_world_units) {
|
||||
}
|
||||
|
||||
LayoutColumn *GetLayoutColumn(LayoutRow *row, float xpos_window_buffer_world_units) {
|
||||
ProfileFunction();
|
||||
if (!row) return NULL;
|
||||
For(row->columns) {
|
||||
if (xpos_window_buffer_world_units >= it.rect.min.x && xpos_window_buffer_world_units <= it.rect.max.x) {
|
||||
@@ -255,6 +265,7 @@ LayoutColumn *GetLayoutColumn(LayoutRow *row, float xpos_window_buffer_world_uni
|
||||
}
|
||||
|
||||
Tuple<LayoutRow *, LayoutColumn *> GetRowCol(Window &window, Vec2 pos_buffer_world_units) {
|
||||
ProfileFunction();
|
||||
Tuple<LayoutRow *, LayoutColumn *> result = {};
|
||||
result.a = GetLayoutRow(window, pos_buffer_world_units.y);
|
||||
result.b = GetLayoutColumn(result.a, pos_buffer_world_units.x);
|
||||
@@ -262,6 +273,7 @@ Tuple<LayoutRow *, LayoutColumn *> GetRowCol(Window &window, Vec2 pos_buffer_wor
|
||||
}
|
||||
|
||||
Tuple<LayoutRow *, LayoutColumn *> GetRowCol(Window &window, int64_t pos) {
|
||||
ProfileFunction();
|
||||
Tuple<LayoutRow *, LayoutColumn *> result = {};
|
||||
For(window.layout.rows) {
|
||||
if (window.layout.rows.len == 0) continue;
|
||||
@@ -286,6 +298,7 @@ Tuple<LayoutRow *, LayoutColumn *> GetRowCol(Window &window, int64_t pos) {
|
||||
}
|
||||
|
||||
Range CalculateVisibleLineRange(Window &window) {
|
||||
ProfileFunction();
|
||||
Rect2 visible_rect = {window.scroll, window.scroll + GetSize(window.rect)};
|
||||
Range result = {-1, -1};
|
||||
For(window.layout.rows) {
|
||||
@@ -302,6 +315,7 @@ Range CalculateVisibleLineRange(Window &window) {
|
||||
}
|
||||
|
||||
Array<LayoutColumn> CalculateVisibleColumns(Arena *arena, Window &window) {
|
||||
ProfileFunction();
|
||||
Range visible_line_range = CalculateVisibleLineRange(window);
|
||||
Array<LayoutColumn> r = {*arena};
|
||||
for (int64_t line = visible_line_range.min; line < visible_line_range.max && line >= 0 && line < window.layout.rows.len; line += 1) {
|
||||
@@ -316,6 +330,7 @@ Array<LayoutColumn> CalculateVisibleColumns(Arena *arena, Window &window) {
|
||||
}
|
||||
|
||||
void MergeCursors(Window *window) {
|
||||
ProfileFunction();
|
||||
Scratch scratch;
|
||||
// Merge cursors that overlap, this needs to be handled before any edits to
|
||||
// make sure overlapping edits won't happen.
|
||||
@@ -351,12 +366,14 @@ void MergeCursors(Window *window) {
|
||||
}
|
||||
|
||||
void SaveHistoryBeforeMergeCursor(Window *window, Array<HistoryEntry> *stack) {
|
||||
ProfileFunction();
|
||||
HistoryEntry *entry = stack->alloc();
|
||||
Allocator sys_allocator = GetSystemAllocator();
|
||||
entry->cursors = window->cursors.tight_copy(sys_allocator);
|
||||
}
|
||||
|
||||
void SaveHistoryBeforeApplyEdits(Window *window, Array<HistoryEntry> *stack, Array<Edit> edits) {
|
||||
ProfileFunction();
|
||||
HistoryEntry *entry = stack->last();
|
||||
Allocator sys_allocator = GetSystemAllocator();
|
||||
entry->edits = edits.tight_copy(sys_allocator);
|
||||
@@ -390,6 +407,7 @@ void SaveHistoryBeforeApplyEdits(Window *window, Array<HistoryEntry> *stack, Arr
|
||||
}
|
||||
|
||||
void RedoEdit(Window *window) {
|
||||
ProfileFunction();
|
||||
if (window->history.redo_stack.len <= 0) return;
|
||||
|
||||
HistoryEntry entry = window->history.redo_stack.pop();
|
||||
@@ -399,7 +417,8 @@ void RedoEdit(Window *window) {
|
||||
_ApplyEdits(&window->buffer, entry.edits);
|
||||
|
||||
window->cursors.dealloc();
|
||||
window->cursors = entry.cursors;
|
||||
window->cursors = entry.cursors;
|
||||
window->not_regen_layout = false;
|
||||
|
||||
Allocator sys_allocator = GetSystemAllocator();
|
||||
For(entry.edits) Dealloc(sys_allocator, &it.string.data);
|
||||
@@ -407,6 +426,7 @@ void RedoEdit(Window *window) {
|
||||
}
|
||||
|
||||
void UndoEdit(Window *window) {
|
||||
ProfileFunction();
|
||||
if (window->history.undo_stack.len <= 0) return;
|
||||
|
||||
HistoryEntry entry = window->history.undo_stack.pop();
|
||||
@@ -416,7 +436,8 @@ void UndoEdit(Window *window) {
|
||||
_ApplyEdits(&window->buffer, entry.edits);
|
||||
|
||||
window->cursors.dealloc();
|
||||
window->cursors = entry.cursors;
|
||||
window->cursors = entry.cursors;
|
||||
window->not_regen_layout = false;
|
||||
|
||||
Allocator sys_allocator = GetSystemAllocator();
|
||||
For(entry.edits) Dealloc(sys_allocator, &it.string.data);
|
||||
@@ -442,6 +463,7 @@ void BeforeEdit(Window *window) {
|
||||
}
|
||||
|
||||
void ApplyEdits(Window *window, Array<Edit> edits) {
|
||||
ProfileFunction();
|
||||
Assert(window->history.debug_edit_phase == 1);
|
||||
window->history.debug_edit_phase += 1;
|
||||
SaveHistoryBeforeApplyEdits(window, &window->history.undo_stack, edits);
|
||||
@@ -449,6 +471,7 @@ void ApplyEdits(Window *window, Array<Edit> edits) {
|
||||
}
|
||||
|
||||
void AfterEdit(Window *window, Array<Edit> edits) {
|
||||
ProfileFunction();
|
||||
Assert(window->history.debug_edit_phase == 2);
|
||||
window->history.debug_edit_phase -= 2;
|
||||
|
||||
@@ -484,4 +507,6 @@ void AfterEdit(Window *window, Array<Edit> edits) {
|
||||
|
||||
// Make sure all cursors are in range
|
||||
For(window->cursors) it.range = Clamp(window->buffer, it.range);
|
||||
|
||||
window->not_regen_layout = false;
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
#include "../basic/basic.h"
|
||||
#include "raylib.h"
|
||||
#include "raymath.h"
|
||||
#include "profiler.cpp"
|
||||
|
||||
// @todo: highlight all occurences of selected word
|
||||
// @todo: systematic way of coloring words
|
||||
// @todo: search for word
|
||||
// @todo: context menu
|
||||
// @todo: toy around with acme ideas
|
||||
@@ -63,6 +63,7 @@ bool IsDoubleClick() {
|
||||
}
|
||||
|
||||
int main() {
|
||||
BeginProfiler();
|
||||
InitScratch();
|
||||
InitWindow(800, 600, "Hello");
|
||||
// @todo: dpi
|
||||
@@ -123,6 +124,7 @@ int main() {
|
||||
UpdateEventRecording();
|
||||
|
||||
{
|
||||
ProfileScope(focused_window_input_update);
|
||||
Window *focused_window = &windows[0];
|
||||
focused_window->start_rect = GetScreenRect();
|
||||
|
||||
@@ -428,28 +430,31 @@ int main() {
|
||||
}
|
||||
|
||||
{
|
||||
String seek = "number";
|
||||
int64_t index = 0;
|
||||
int64_t base_index = 0;
|
||||
String s = GetString(focused_window->buffer);
|
||||
while (Seek(s, seek, &index)) {
|
||||
Range range = {base_index + index, base_index + index + seek.len};
|
||||
Cursor cursor = focused_window->cursors[0];
|
||||
if (GetRangeSize(cursor.range)) {
|
||||
String seek = GetString(focused_window->buffer, cursor.range);
|
||||
int64_t index = 0;
|
||||
int64_t base_index = 0;
|
||||
String s = GetString(focused_window->buffer);
|
||||
while (Seek(s, seek, &index)) {
|
||||
Range range = {base_index + index, base_index + index + seek.len};
|
||||
|
||||
ColoredString colored = {};
|
||||
colored.range = range;
|
||||
colored.use_text_color = true;
|
||||
colored.use_underline = true;
|
||||
colored.text_color = RED;
|
||||
focused_window->colored_strings.add(colored);
|
||||
// ColoredString colored = {};
|
||||
// colored.range = range;
|
||||
// colored.use_text_color = true;
|
||||
// colored.use_underline = true;
|
||||
// colored.text_color = RED;
|
||||
// focused_window->colored_strings.add(colored);
|
||||
|
||||
colored = {};
|
||||
colored.range = range;
|
||||
colored.use_highlight_background_color = true;
|
||||
colored.highlight_background_color = {0, 50, 150, 50};
|
||||
focused_window->colored_strings.add(colored);
|
||||
ColoredString colored = {};
|
||||
colored.range = range;
|
||||
colored.use_highlight_background_color = true;
|
||||
colored.highlight_background_color = {0, 0, 0, 25};
|
||||
focused_window->colored_strings.add(colored);
|
||||
|
||||
base_index += index + seek.len;
|
||||
s = s.skip(index + seek.len);
|
||||
base_index += index + seek.len;
|
||||
s = s.skip(index + seek.len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,15 +465,17 @@ int main() {
|
||||
ClearBackground(RAYWHITE);
|
||||
|
||||
ForItem(window, windows) {
|
||||
ProfileScope(window_render_loop);
|
||||
window.rect = window.start_rect;
|
||||
Rect2 horizontal_bar_rect = CutBottom(&window.rect, 10);
|
||||
Rect2 vertical_bar_rect = CutRight(&window.rect, 10);
|
||||
Rect2 line_number_rect = CutLeft(&window.rect, MeasureString(window.font, "1234", window.font_size, window.font_spacing).x);
|
||||
RegenLayout(&window);
|
||||
if (!window.not_regen_layout) RegenLayout(&window);
|
||||
|
||||
// Draw and layout window overlay
|
||||
Vec2 mouse = GetMousePosition();
|
||||
{
|
||||
ProfileScope(draw_rectangles);
|
||||
DrawRectangleRec(ToRectangle(window.rect), WHITE);
|
||||
DrawRectangleRec(ToRectangle(line_number_rect), WHITE);
|
||||
|
||||
@@ -659,11 +666,16 @@ int main() {
|
||||
Array<LayoutColumn> visible_columns = CalculateVisibleColumns(&FrameArena, window);
|
||||
Range visible_line_range = CalculateVisibleLineRange(window);
|
||||
|
||||
BeginProfileScope("draw_glyphs");
|
||||
ForItem(col, visible_columns) {
|
||||
Rect2 rect = {col.rect.min + window.rect.min - window.scroll, col.rect.max + window.rect.min - window.scroll};
|
||||
|
||||
if (col.codepoint == '\n') {
|
||||
DrawTextEx(font, "\\n", rect.min, font_size, font_spacing, GRAY);
|
||||
// DrawTextEx(font, "\\n", rect.min, font_size, font_spacing, GRAY);
|
||||
DrawTextCodepoint(font, 'n', rect.min, font_size, GRAY);
|
||||
} else if (col.codepoint == '\r') {
|
||||
// DrawTextEx(font, "\\r", rect.min, font_size, font_spacing, GRAY);
|
||||
DrawTextCodepoint(font, 'r', rect.min, font_size, GRAY);
|
||||
} else if (col.codepoint == '\0') {
|
||||
DrawTextEx(font, "\\0", rect.min, font_size, font_spacing, {255, 0, 0, 150});
|
||||
} else if ((col.codepoint != ' ') && (col.codepoint != '\t')) {
|
||||
@@ -693,8 +705,10 @@ int main() {
|
||||
}
|
||||
}
|
||||
}
|
||||
EndProfileScope();
|
||||
|
||||
// Draw cursor stuff
|
||||
BeginProfileScope("draw_cursor");
|
||||
Array<LayoutRow *> lines_to_highlight = {FrameArena};
|
||||
ForItem(cursor, window.cursors) {
|
||||
Tuple<LayoutRow *, LayoutColumn *> front = GetRowCol(window, GetFront(cursor));
|
||||
@@ -711,27 +725,29 @@ int main() {
|
||||
For(visible_columns) {
|
||||
if (it.pos >= cursor.range.min && it.pos < cursor.range.max) {
|
||||
Rect2 rect = it.rect + window.rect.min - window.scroll;
|
||||
DrawRectangleRec(ToRectangle(rect), {0, 50, 150, 50});
|
||||
DrawRectangleRec(ToRectangle(rect), {0, 0, 0, 75});
|
||||
}
|
||||
}
|
||||
|
||||
// Cursor blocks
|
||||
if (front.b) {
|
||||
Rect2 rect = front.b->rect + window.rect.min - window.scroll;
|
||||
rect = CutLeft(&rect, 4);
|
||||
DrawRectangleRec(ToRectangle(rect), RED);
|
||||
rect = CutLeft(&rect, 2);
|
||||
DrawRectangleRec(ToRectangle(rect), {0, 0, 0, 255});
|
||||
}
|
||||
if (back.b) {
|
||||
Rect2 rect = back.b->rect + window.rect.min - window.scroll;
|
||||
rect = CutLeft(&rect, 2);
|
||||
DrawRectangleRec(ToRectangle(rect), GREEN);
|
||||
rect = CutLeft(&rect, 1);
|
||||
DrawRectangleRec(ToRectangle(rect), {0, 0, 0, 255});
|
||||
}
|
||||
}
|
||||
EndProfileScope();
|
||||
|
||||
EndScissorMode();
|
||||
|
||||
// Draw line numbers
|
||||
{
|
||||
ProfileScope(draw_line_numbers);
|
||||
BeginScissorMode((int)line_number_rect.min.x, (int)line_number_rect.min.y, (int)(line_number_rect.max.x - line_number_rect.min.x), (int)(line_number_rect.max.y - line_number_rect.min.y));
|
||||
for (int64_t line = visible_line_range.min; line < visible_line_range.max; line += 1) {
|
||||
LayoutRow &row = window.layout.rows[line];
|
||||
@@ -765,4 +781,6 @@ int main() {
|
||||
|
||||
EndDrawing();
|
||||
}
|
||||
CloseWindow();
|
||||
EndProfiler();
|
||||
}
|
||||
50
src/text_editor/profiler.cpp
Normal file
50
src/text_editor/profiler.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
#if 1
|
||||
#include "spall.h"
|
||||
|
||||
static SpallProfile spall_ctx;
|
||||
static SpallBuffer spall_buffer;
|
||||
double get_time_in_micros(void);
|
||||
|
||||
void BeginProfiler() {
|
||||
spall_ctx = spall_init_file("hello_world.spall", 1);
|
||||
|
||||
int buffer_size = 1 * 1024 * 1024;
|
||||
unsigned char *buffer = (unsigned char *)malloc(buffer_size);
|
||||
spall_buffer = {buffer, (size_t)buffer_size};
|
||||
|
||||
spall_buffer_init(&spall_ctx, &spall_buffer);
|
||||
}
|
||||
|
||||
void EndProfiler() {
|
||||
spall_buffer_quit(&spall_ctx, &spall_buffer);
|
||||
free(spall_buffer.data);
|
||||
spall_quit(&spall_ctx);
|
||||
}
|
||||
|
||||
void BeginProfileScope(const char *name) {
|
||||
spall_buffer_begin(&spall_ctx, &spall_buffer,
|
||||
name, // name of your name
|
||||
sizeof(name) - 1, // name len minus the null terminator
|
||||
get_time_in_micros() // timestamp in microseconds -- start of your timing block
|
||||
);
|
||||
}
|
||||
|
||||
void EndProfileScope() {
|
||||
spall_buffer_end(&spall_ctx, &spall_buffer,
|
||||
get_time_in_micros() // timestamp in microseconds -- end of your timing block
|
||||
);
|
||||
}
|
||||
#define ProfileScope(name) ProfileScope_ PROFILE_SCOPE_VAR_##name(#name)
|
||||
#define ProfileFunction() ProfileScope_ PROFILE_SCOPE_FUNCTION(__FUNCTION__)
|
||||
struct ProfileScope_ {
|
||||
ProfileScope_(const char *name) { BeginProfileScope(name); }
|
||||
~ProfileScope_() { EndProfileScope(); }
|
||||
};
|
||||
#else
|
||||
#define ProfileScope(name)
|
||||
#define ProfileFunction()
|
||||
#define BeginProfiler()
|
||||
#define EndProfiler()
|
||||
#define BeginProfileScope(name)
|
||||
#define EndProfileScope()
|
||||
#endif
|
||||
443
src/text_editor/spall.h
Normal file
443
src/text_editor/spall.h
Normal file
@@ -0,0 +1,443 @@
|
||||
// SPDX-FileCopyrightText: © 2023 Phillip Trudeau-Tavara <pmttavara@protonmail.com>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/*
|
||||
|
||||
TODO: Optional Helper APIs:
|
||||
|
||||
- Compression API: would require a mutexed lockable context (yuck...)
|
||||
- Either using a ZIP library, a name cache + TIDPID cache, or both (but ZIP is likely more than enough!!!)
|
||||
- begin()/end() writes compressed chunks to a caller-determined destination
|
||||
- The destination can be the buffered-writing API or a custom user destination
|
||||
- Ultimately need to take a lock with some granularity... can that be the caller's responsibility?
|
||||
|
||||
- Counter Event: should allow tracking arbitrary named values with a single event, for memory and frame profiling
|
||||
|
||||
- Ring-buffer API
|
||||
spall_ring_init
|
||||
spall_ring_emit_begin
|
||||
spall_ring_emit_end
|
||||
spall_ring_flush
|
||||
*/
|
||||
|
||||
#ifndef SPALL_H
|
||||
#define SPALL_H
|
||||
|
||||
#if !defined(_MSC_VER) || defined(__clang__)
|
||||
#define SPALL_NOINSTRUMENT __attribute__((no_instrument_function))
|
||||
#define SPALL_FORCEINLINE __attribute__((always_inline))
|
||||
#else
|
||||
#define SPALL_NOINSTRUMENT // Can't noinstrument on MSVC!
|
||||
#define SPALL_FORCEINLINE __forceinline
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define SPALL_FN static inline SPALL_NOINSTRUMENT
|
||||
|
||||
#define SPALL_MIN(a, b) (((a) < (b)) ? (a) : (b))
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
typedef struct SpallHeader {
|
||||
uint64_t magic_header; // = 0x0BADF00D
|
||||
uint64_t version; // = 1
|
||||
double timestamp_unit;
|
||||
uint64_t must_be_0;
|
||||
} SpallHeader;
|
||||
|
||||
enum {
|
||||
SpallEventType_Invalid = 0,
|
||||
SpallEventType_Custom_Data = 1, // Basic readers can skip this.
|
||||
SpallEventType_StreamOver = 2,
|
||||
|
||||
SpallEventType_Begin = 3,
|
||||
SpallEventType_End = 4,
|
||||
SpallEventType_Instant = 5,
|
||||
|
||||
SpallEventType_Overwrite_Timestamp = 6, // Retroactively change timestamp units - useful for incrementally improving RDTSC frequency.
|
||||
SpallEventType_Pad_Skip = 7,
|
||||
};
|
||||
|
||||
typedef struct SpallBeginEvent {
|
||||
uint8_t type; // = SpallEventType_Begin
|
||||
uint8_t category;
|
||||
|
||||
uint32_t pid;
|
||||
uint32_t tid;
|
||||
double when;
|
||||
|
||||
uint8_t name_length;
|
||||
uint8_t args_length;
|
||||
} SpallBeginEvent;
|
||||
|
||||
typedef struct SpallBeginEventMax {
|
||||
SpallBeginEvent event;
|
||||
char name_bytes[255];
|
||||
char args_bytes[255];
|
||||
} SpallBeginEventMax;
|
||||
|
||||
typedef struct SpallEndEvent {
|
||||
uint8_t type; // = SpallEventType_End
|
||||
uint32_t pid;
|
||||
uint32_t tid;
|
||||
double when;
|
||||
} SpallEndEvent;
|
||||
|
||||
typedef struct SpallPadSkipEvent {
|
||||
uint8_t type; // = SpallEventType_Pad_Skip
|
||||
uint32_t size;
|
||||
} SpallPadSkipEvent;
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
typedef struct SpallProfile SpallProfile;
|
||||
|
||||
// Important!: If you define your own callbacks, mark them SPALL_NOINSTRUMENT!
|
||||
typedef bool (*SpallWriteCallback)(SpallProfile *self, const void *data, size_t length);
|
||||
typedef bool (*SpallFlushCallback)(SpallProfile *self);
|
||||
typedef void (*SpallCloseCallback)(SpallProfile *self);
|
||||
|
||||
struct SpallProfile {
|
||||
double timestamp_unit;
|
||||
bool is_json;
|
||||
SpallWriteCallback write;
|
||||
SpallFlushCallback flush;
|
||||
SpallCloseCallback close;
|
||||
void *data;
|
||||
};
|
||||
|
||||
// Important!: If you are writing Begin/End events, then do NOT write
|
||||
// events for the same PID + TID pair on different buffers!!!
|
||||
typedef struct SpallBuffer {
|
||||
void *data;
|
||||
size_t length;
|
||||
|
||||
// Internal data - don't assign this
|
||||
size_t head;
|
||||
SpallProfile *ctx;
|
||||
} SpallBuffer;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if defined(SPALL_BUFFER_PROFILING) && !defined(SPALL_BUFFER_PROFILING_GET_TIME)
|
||||
#error "You must #define SPALL_BUFFER_PROFILING_GET_TIME() to profile buffer flushes."
|
||||
#endif
|
||||
|
||||
SPALL_FN SPALL_FORCEINLINE void spall__buffer_profile(SpallProfile *ctx, SpallBuffer *wb, double spall_time_begin, double spall_time_end, const char *name, int name_len);
|
||||
#ifdef SPALL_BUFFER_PROFILING
|
||||
#define SPALL_BUFFER_PROFILE_BEGIN() double spall_time_begin = (SPALL_BUFFER_PROFILING_GET_TIME())
|
||||
// Don't call this with anything other than a string literal
|
||||
#define SPALL_BUFFER_PROFILE_END(name) spall__buffer_profile(ctx, wb, spall_time_begin, (SPALL_BUFFER_PROFILING_GET_TIME()), "" name "", sizeof("" name "") - 1)
|
||||
#else
|
||||
#define SPALL_BUFFER_PROFILE_BEGIN()
|
||||
#define SPALL_BUFFER_PROFILE_END(name)
|
||||
#endif
|
||||
|
||||
SPALL_FN SPALL_FORCEINLINE bool spall__file_write(SpallProfile *ctx, const void *p, size_t n) {
|
||||
if (!ctx->data) return false;
|
||||
#ifdef SPALL_DEBUG
|
||||
if (feof((FILE *)ctx->data)) return false;
|
||||
if (ferror((FILE *)ctx->data)) return false;
|
||||
#endif
|
||||
|
||||
if (fwrite(p, n, 1, (FILE *)ctx->data) != 1) return false;
|
||||
return true;
|
||||
}
|
||||
SPALL_FN bool spall__file_flush(SpallProfile *ctx) {
|
||||
if (!ctx->data) return false;
|
||||
if (fflush((FILE *)ctx->data)) return false;
|
||||
return true;
|
||||
}
|
||||
SPALL_FN void spall__file_close(SpallProfile *ctx) {
|
||||
if (!ctx->data) return;
|
||||
|
||||
if (ctx->is_json) {
|
||||
#ifdef SPALL_DEBUG
|
||||
if (!feof((FILE *)ctx->data) && !ferror((FILE *)ctx->data))
|
||||
#endif
|
||||
{
|
||||
fseek((FILE *)ctx->data, -2, SEEK_CUR); // seek back to overwrite trailing comma
|
||||
fwrite("\n]}\n", sizeof("\n]}\n") - 1, 1, (FILE *)ctx->data);
|
||||
}
|
||||
}
|
||||
fflush((FILE *)ctx->data);
|
||||
fclose((FILE *)ctx->data);
|
||||
ctx->data = NULL;
|
||||
}
|
||||
|
||||
SPALL_FN SPALL_FORCEINLINE bool spall__buffer_flush(SpallProfile *ctx, SpallBuffer *wb) {
|
||||
// precon: wb
|
||||
// precon: wb->data
|
||||
// precon: wb->head <= wb->length
|
||||
// precon: !ctx || ctx->write
|
||||
#ifdef SPALL_DEBUG
|
||||
if (wb->ctx != ctx) return false; // Buffer must be bound to this context (or to NULL)
|
||||
#endif
|
||||
|
||||
if (wb->head && ctx) {
|
||||
SPALL_BUFFER_PROFILE_BEGIN();
|
||||
if (!ctx->write) return false;
|
||||
if (ctx->write == spall__file_write) {
|
||||
if (!spall__file_write(ctx, wb->data, wb->head)) return false;
|
||||
} else {
|
||||
if (!ctx->write(ctx, wb->data, wb->head)) return false;
|
||||
}
|
||||
SPALL_BUFFER_PROFILE_END("Buffer Flush");
|
||||
}
|
||||
wb->head = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
SPALL_FN SPALL_FORCEINLINE bool spall__buffer_write(SpallProfile *ctx, SpallBuffer *wb, void *p, size_t n) {
|
||||
// precon: !wb || wb->head < wb->length
|
||||
// precon: !ctx || ctx->write
|
||||
if (!wb) return ctx->write && ctx->write(ctx, p, n);
|
||||
#ifdef SPALL_DEBUG
|
||||
if (wb->ctx != ctx) return false; // Buffer must be bound to this context (or to NULL)
|
||||
#endif
|
||||
if (wb->head + n > wb->length && !spall__buffer_flush(ctx, wb)) return false;
|
||||
if (n > wb->length) {
|
||||
SPALL_BUFFER_PROFILE_BEGIN();
|
||||
if (!ctx->write || !ctx->write(ctx, p, n)) return false;
|
||||
SPALL_BUFFER_PROFILE_END("Unbuffered Write");
|
||||
return true;
|
||||
}
|
||||
memcpy((char *)wb->data + wb->head, p, n);
|
||||
wb->head += n;
|
||||
return true;
|
||||
}
|
||||
|
||||
SPALL_FN bool spall_buffer_flush(SpallProfile *ctx, SpallBuffer *wb) {
|
||||
#ifdef SPALL_DEBUG
|
||||
if (!wb) return false;
|
||||
if (!wb->data) return false;
|
||||
#endif
|
||||
|
||||
if (!spall__buffer_flush(ctx, wb)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
SPALL_FN bool spall_buffer_init(SpallProfile *ctx, SpallBuffer *wb) {
|
||||
if (!spall_buffer_flush(NULL, wb)) return false;
|
||||
wb->ctx = ctx;
|
||||
return true;
|
||||
}
|
||||
SPALL_FN bool spall_buffer_quit(SpallProfile *ctx, SpallBuffer *wb) {
|
||||
if (!spall_buffer_flush(ctx, wb)) return false;
|
||||
wb->ctx = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
SPALL_FN bool spall_buffer_abort(SpallBuffer *wb) {
|
||||
if (!wb) return false;
|
||||
wb->ctx = NULL;
|
||||
if (!spall__buffer_flush(NULL, wb)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
SPALL_FN size_t spall_build_header(void *buffer, size_t rem_size, double timestamp_unit) {
|
||||
size_t header_size = sizeof(SpallHeader);
|
||||
if (header_size > rem_size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
SpallHeader *header = (SpallHeader *)buffer;
|
||||
header->magic_header = 0x0BADF00D;
|
||||
header->version = 1;
|
||||
header->timestamp_unit = timestamp_unit;
|
||||
header->must_be_0 = 0;
|
||||
return header_size;
|
||||
}
|
||||
SPALL_FN SPALL_FORCEINLINE size_t spall_build_begin(void *buffer, size_t rem_size, const char *name, signed long name_len, const char *args, signed long args_len, double when, uint32_t tid, uint32_t pid) {
|
||||
SpallBeginEventMax *ev = (SpallBeginEventMax *)buffer;
|
||||
uint8_t trunc_name_len = (uint8_t)SPALL_MIN(name_len, 255); // will be interpreted as truncated in the app (?)
|
||||
uint8_t trunc_args_len = (uint8_t)SPALL_MIN(args_len, 255); // will be interpreted as truncated in the app (?)
|
||||
|
||||
size_t ev_size = sizeof(SpallBeginEvent) + trunc_name_len + trunc_args_len;
|
||||
if (ev_size > rem_size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ev->event.type = SpallEventType_Begin;
|
||||
ev->event.category = 0;
|
||||
ev->event.pid = pid;
|
||||
ev->event.tid = tid;
|
||||
ev->event.when = when;
|
||||
ev->event.name_length = trunc_name_len;
|
||||
ev->event.args_length = trunc_args_len;
|
||||
memcpy(ev->name_bytes, name, trunc_name_len);
|
||||
memcpy(ev->name_bytes + trunc_name_len, args, trunc_args_len);
|
||||
|
||||
return ev_size;
|
||||
}
|
||||
SPALL_FN SPALL_FORCEINLINE size_t spall_build_end(void *buffer, size_t rem_size, double when, uint32_t tid, uint32_t pid) {
|
||||
size_t ev_size = sizeof(SpallEndEvent);
|
||||
if (ev_size > rem_size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
SpallEndEvent *ev = (SpallEndEvent *)buffer;
|
||||
ev->type = SpallEventType_End;
|
||||
ev->pid = pid;
|
||||
ev->tid = tid;
|
||||
ev->when = when;
|
||||
|
||||
return ev_size;
|
||||
}
|
||||
|
||||
SPALL_FN void spall_quit(SpallProfile *ctx) {
|
||||
if (!ctx) return;
|
||||
if (ctx->close) ctx->close(ctx);
|
||||
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
}
|
||||
|
||||
SPALL_FN SpallProfile spall_init_callbacks(double timestamp_unit,
|
||||
SpallWriteCallback write,
|
||||
SpallFlushCallback flush,
|
||||
SpallCloseCallback close,
|
||||
void *userdata,
|
||||
bool is_json) {
|
||||
SpallProfile ctx;
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
if (timestamp_unit < 0) return ctx;
|
||||
ctx.timestamp_unit = timestamp_unit;
|
||||
ctx.is_json = is_json;
|
||||
ctx.data = userdata;
|
||||
ctx.write = write;
|
||||
ctx.flush = flush;
|
||||
ctx.close = close;
|
||||
|
||||
if (ctx.is_json) {
|
||||
if (!ctx.write(&ctx, "{\"traceEvents\":[\n", sizeof("{\"traceEvents\":[\n") - 1)) {
|
||||
spall_quit(&ctx);
|
||||
return ctx;
|
||||
}
|
||||
} else {
|
||||
SpallHeader header;
|
||||
size_t len = spall_build_header(&header, sizeof(header), timestamp_unit);
|
||||
if (!ctx.write(&ctx, &header, len)) {
|
||||
spall_quit(&ctx);
|
||||
return ctx;
|
||||
}
|
||||
}
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
SPALL_FN SpallProfile spall_init_file_ex(const char *filename, double timestamp_unit, bool is_json) {
|
||||
SpallProfile ctx;
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
if (!filename) return ctx;
|
||||
ctx.data = fopen(filename, "wb"); // TODO: handle utf8 and long paths on windows
|
||||
if (ctx.data) { // basically freopen() but we don't want to force users to lug along another macro define
|
||||
fclose((FILE *)ctx.data);
|
||||
ctx.data = fopen(filename, "ab");
|
||||
}
|
||||
if (!ctx.data) {
|
||||
spall_quit(&ctx);
|
||||
return ctx;
|
||||
}
|
||||
ctx = spall_init_callbacks(timestamp_unit, spall__file_write, spall__file_flush, spall__file_close, ctx.data, is_json);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
SPALL_FN SpallProfile spall_init_file(const char *filename, double timestamp_unit) { return spall_init_file_ex(filename, timestamp_unit, false); }
|
||||
SPALL_FN SpallProfile spall_init_file_json(const char *filename, double timestamp_unit) { return spall_init_file_ex(filename, timestamp_unit, true); }
|
||||
|
||||
SPALL_FN bool spall_flush(SpallProfile *ctx) {
|
||||
#ifdef SPALL_DEBUG
|
||||
if (!ctx) return false;
|
||||
#endif
|
||||
|
||||
if (!ctx->flush || !ctx->flush(ctx)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
SPALL_FN SPALL_FORCEINLINE bool spall_buffer_begin_args(SpallProfile *ctx, SpallBuffer *wb, const char *name, signed long name_len, const char *args, signed long args_len, double when, uint32_t tid, uint32_t pid) {
|
||||
#ifdef SPALL_DEBUG
|
||||
if (!ctx) return false;
|
||||
if (!name) return false;
|
||||
if (name_len <= 0) return false;
|
||||
if (!wb) return false;
|
||||
#endif
|
||||
|
||||
if (ctx->is_json) {
|
||||
char buf[1024];
|
||||
int buf_len = snprintf(buf, sizeof(buf),
|
||||
"{\"ph\":\"B\",\"ts\":%f,\"pid\":%u,\"tid\":%u,\"name\":\"%.*s\",\"args\":\"%.*s\"},\n",
|
||||
when * ctx->timestamp_unit, pid, tid, (int)(uint8_t)name_len, name, (int)(uint8_t)args_len, args);
|
||||
if (buf_len <= 0) return false;
|
||||
if (buf_len >= sizeof(buf)) return false;
|
||||
if (!spall__buffer_write(ctx, wb, buf, buf_len)) return false;
|
||||
} else {
|
||||
if ((wb->head + sizeof(SpallBeginEventMax)) > wb->length) {
|
||||
if (!spall__buffer_flush(ctx, wb)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
wb->head += spall_build_begin((char *)wb->data + wb->head, wb->length - wb->head, name, name_len, args, args_len, when, tid, pid);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SPALL_FN SPALL_FORCEINLINE bool spall_buffer_begin_ex(SpallProfile *ctx, SpallBuffer *wb, const char *name, signed long name_len, double when, uint32_t tid, uint32_t pid) {
|
||||
return spall_buffer_begin_args(ctx, wb, name, name_len, "", 0, when, tid, pid);
|
||||
}
|
||||
|
||||
SPALL_FN bool spall_buffer_begin(SpallProfile *ctx, SpallBuffer *wb, const char *name, signed long name_len, double when) {
|
||||
return spall_buffer_begin_args(ctx, wb, name, name_len, "", 0, when, 0, 0);
|
||||
}
|
||||
|
||||
SPALL_FN SPALL_FORCEINLINE bool spall_buffer_end_ex(SpallProfile *ctx, SpallBuffer *wb, double when, uint32_t tid, uint32_t pid) {
|
||||
#ifdef SPALL_DEBUG
|
||||
if (!ctx) return false;
|
||||
if (!wb) return false;
|
||||
#endif
|
||||
|
||||
if (ctx->is_json) {
|
||||
char buf[512];
|
||||
int buf_len = snprintf(buf, sizeof(buf),
|
||||
"{\"ph\":\"E\",\"ts\":%f,\"pid\":%u,\"tid\":%u},\n",
|
||||
when * ctx->timestamp_unit, pid, tid);
|
||||
if (buf_len <= 0) return false;
|
||||
if (buf_len >= sizeof(buf)) return false;
|
||||
if (!spall__buffer_write(ctx, wb, buf, buf_len)) return false;
|
||||
} else {
|
||||
if ((wb->head + sizeof(SpallEndEvent)) > wb->length) {
|
||||
if (!spall__buffer_flush(ctx, wb)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
wb->head += spall_build_end((char *)wb->data + wb->head, wb->length - wb->head, when, tid, pid);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SPALL_FN bool spall_buffer_end(SpallProfile *ctx, SpallBuffer *wb, double when) { return spall_buffer_end_ex(ctx, wb, when, 0, 0); }
|
||||
|
||||
SPALL_FN SPALL_FORCEINLINE void spall__buffer_profile(SpallProfile *ctx, SpallBuffer *wb, double spall_time_begin, double spall_time_end, const char *name, int name_len) {
|
||||
// precon: ctx
|
||||
// precon: ctx->write
|
||||
char temp_buffer_data[2048];
|
||||
SpallBuffer temp_buffer = {temp_buffer_data, sizeof(temp_buffer_data)};
|
||||
if (!spall_buffer_begin_ex(ctx, &temp_buffer, name, name_len, spall_time_begin, (uint32_t)(uintptr_t)wb->data, 4222222222)) return;
|
||||
if (!spall_buffer_end_ex(ctx, &temp_buffer, spall_time_end, (uint32_t)(uintptr_t)wb->data, 4222222222)) return;
|
||||
if (ctx->write) ctx->write(ctx, temp_buffer_data, temp_buffer.head);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // SPALL_H
|
||||
Reference in New Issue
Block a user