diff --git a/csvexport/build/unix/Makefile b/csvexport/build/unix/Makefile
new file mode 100644
index 00000000..3b50301c
--- /dev/null
+++ b/csvexport/build/unix/Makefile
@@ -0,0 +1,12 @@
+all: debug
+
+debug:
+ @+make -f debug.mk all
+
+release:
+ @+make -f release.mk all
+
+clean:
+ @+make -f build.mk clean
+
+.PHONY: all clean debug release
diff --git a/csvexport/build/unix/build.mk b/csvexport/build/unix/build.mk
new file mode 100644
index 00000000..a7a67460
--- /dev/null
+++ b/csvexport/build/unix/build.mk
@@ -0,0 +1,60 @@
+CFLAGS +=
+CXXFLAGS := $(CFLAGS) -std=gnu++17
+# DEFINES += -DTRACY_NO_STATISTICS
+INCLUDES := $(shell pkg-config --cflags capstone)
+LIBS := $(shell pkg-config --libs capstone) -lpthread
+PROJECT := csvexport
+IMAGE := $(PROJECT)-$(BUILD)
+
+FILTER :=
+
+BASE := $(shell egrep 'ClCompile.*cpp"' ../win32/$(PROJECT).vcxproj | sed -e 's/.*\"\(.*\)\".*/\1/' | sed -e 's@\\@/@g')
+BASE2 := $(shell egrep 'ClCompile.*c"' ../win32/$(PROJECT).vcxproj | sed -e 's/.*\"\(.*\)\".*/\1/' | sed -e 's@\\@/@g')
+
+SRC := $(filter-out $(FILTER),$(BASE))
+SRC2 := $(filter-out $(FILTER),$(BASE2))
+
+TBB := $(shell ld -ltbb -o /dev/null 2>/dev/null; echo $$?)
+ifeq ($(TBB),0)
+ LIBS += -ltbb
+endif
+
+OBJDIRBASE := obj/$(BUILD)
+OBJDIR := $(OBJDIRBASE)/o/o/o
+
+OBJ := $(addprefix $(OBJDIR)/,$(SRC:%.cpp=%.o))
+OBJ2 := $(addprefix $(OBJDIR)/,$(SRC2:%.c=%.o))
+
+all: $(IMAGE)
+
+$(OBJDIR)/%.o: %.cpp
+ $(CXX) -c $(INCLUDES) $(CXXFLAGS) $(DEFINES) $< -o $@
+
+$(OBJDIR)/%.d : %.cpp
+ @echo Resolving dependencies of $<
+ @mkdir -p $(@D)
+ @$(CXX) -MM $(INCLUDES) $(CXXFLAGS) $(DEFINES) $< > $@.$$$$; \
+ sed 's,.*\.o[ :]*,$(OBJDIR)/$(<:.cpp=.o) $@ : ,g' < $@.$$$$ > $@; \
+ rm -f $@.$$$$
+
+$(OBJDIR)/%.o: %.c
+ $(CC) -c $(INCLUDES) $(CFLAGS) $(DEFINES) $< -o $@
+
+$(OBJDIR)/%.d : %.c
+ @echo Resolving dependencies of $<
+ @mkdir -p $(@D)
+ @$(CC) -MM $(INCLUDES) $(CFLAGS) $(DEFINES) $< > $@.$$$$; \
+ sed 's,.*\.o[ :]*,$(OBJDIR)/$(<:.c=.o) $@ : ,g' < $@.$$$$ > $@; \
+ rm -f $@.$$$$
+
+$(IMAGE): $(OBJ) $(OBJ2)
+ $(CXX) $(CXXFLAGS) $(DEFINES) $(OBJ) $(OBJ2) $(LIBS) -o $@
+
+ifneq "$(MAKECMDGOALS)" "clean"
+-include $(addprefix $(OBJDIR)/,$(SRC:.cpp=.d)) $(addprefix $(OBJDIR)/,$(SRC2:.c=.d))
+endif
+
+clean:
+ rm -rf $(OBJDIRBASE) $(IMAGE)*
+
+.PHONY: clean all
diff --git a/csvexport/build/unix/debug.mk b/csvexport/build/unix/debug.mk
new file mode 100644
index 00000000..04d925a6
--- /dev/null
+++ b/csvexport/build/unix/debug.mk
@@ -0,0 +1,11 @@
+ARCH := $(shell uname -m)
+
+CFLAGS := -g3 -Wall
+DEFINES := -DDEBUG
+BUILD := debug
+
+ifeq ($(ARCH),x86_64)
+CFLAGS += -msse4.1
+endif
+
+include build.mk
diff --git a/csvexport/build/unix/release.mk b/csvexport/build/unix/release.mk
new file mode 100644
index 00000000..b59abd5c
--- /dev/null
+++ b/csvexport/build/unix/release.mk
@@ -0,0 +1,7 @@
+ARCH := $(shell uname -m)
+
+CFLAGS := -O3 -s -march=native
+DEFINES := -DNDEBUG
+BUILD := release
+
+include build.mk
diff --git a/csvexport/build/win32/csvexport.vcxproj b/csvexport/build/win32/csvexport.vcxproj
new file mode 100644
index 00000000..87c3cfe2
--- /dev/null
+++ b/csvexport/build/win32/csvexport.vcxproj
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/csvexport/src/csvexport.cpp b/csvexport/src/csvexport.cpp
new file mode 100644
index 00000000..8b07f485
--- /dev/null
+++ b/csvexport/src/csvexport.cpp
@@ -0,0 +1,275 @@
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include "../../server/TracyFileRead.hpp"
+#include "../../server/TracyWorker.hpp"
+#include "cxxopts.hpp"
+
+struct Args {
+ std::string filter;
+ std::string separator;
+ std::string trace_file;
+ bool case_sensitive;
+ bool self_time;
+ bool unwrap;
+};
+
+Args parse_args(int argc, char** argv)
+{
+ cxxopts::Options options(
+ "extract",
+ "Extract statistics from a trace to a CSV format"
+ );
+
+ std::string filter;
+ std::string separator;
+ std::string trace_file;
+ bool case_sensitive = false;
+ bool self_time = false;
+ bool unwrap = false;
+
+ options.add_options()
+ ("h,help", "Print usage")
+ ("f,filter", "Filter zone names",
+ cxxopts::value(filter)->default_value(""))
+ ("s,separator", "CSV separator",
+ cxxopts::value(separator)->default_value(","))
+ ("t,trace", "same as ",
+ cxxopts::value(trace_file))
+ ("case", "Case sensitive filtering",
+ cxxopts::value(case_sensitive))
+ ("self", "Get self times",
+ cxxopts::value(self_time))
+ ("unwrap", "Report each zone event",
+ cxxopts::value(unwrap))
+ ;
+
+ options.positional_help("");
+ options.parse_positional("trace");
+ auto result = options.parse(argc, argv);
+ if (result.count("help"))
+ {
+ fprintf(stderr, "%s\n", options.help().data());
+ exit(0);
+ }
+
+ if (result.count("trace") == 0)
+ {
+ fprintf(stderr, "Requires a trace file");
+ exit(1);
+ }
+
+ return Args {
+ filter, separator, trace_file, case_sensitive, self_time, unwrap
+ };
+}
+
+bool is_substring(
+ const std::string term,
+ const std::string s,
+ bool case_sensitive = false
+){
+ std::string new_term = term;
+ std::string new_s = s;
+
+ if (!case_sensitive) {
+ std::transform(
+ new_term.begin(),
+ new_term.end(),
+ new_term.begin(),
+ [](unsigned char c){ return std::tolower(c); }
+ );
+
+ std::transform(
+ new_s.begin(),
+ new_s.end(),
+ new_s.begin(),
+ [](unsigned char c){ return std::tolower(c); }
+ );
+ }
+
+ return new_s.find(new_term) != std::string::npos;
+}
+
+const char* get_name(int32_t id, const tracy::Worker& worker)
+{
+ auto& srcloc = worker.GetSourceLocation(id);
+ return worker.GetString(srcloc.name.active ? srcloc.name : srcloc.function);
+}
+
+template
+std::string join(const T& v, std::string sep) {
+ std::ostringstream s;
+ for (const auto& i : v) {
+ if (&i != &v[0]) {
+ s << sep;
+ }
+ s << i;
+ }
+ return s.str();
+}
+
+// From TracyView.cpp
+int64_t GetZoneChildTimeFast(
+ const tracy::Worker& worker,
+ const tracy::ZoneEvent& zone
+){
+ int64_t time = 0;
+ if( zone.HasChildren() )
+ {
+ auto& children = worker.GetZoneChildren( zone.Child() );
+ if( children.is_magic() )
+ {
+ auto& vec = *(tracy::Vector*)&children;
+ for( auto& v : vec )
+ {
+ assert( v.IsEndValid() );
+ time += v.End() - v.Start();
+ }
+ }
+ else
+ {
+ for( auto& v : children )
+ {
+ assert( v->IsEndValid() );
+ time += v->End() - v->Start();
+ }
+ }
+ }
+ return time;
+}
+
+int main(int argc, char** argv)
+{
+ Args args = parse_args(argc, argv);
+
+ auto f = std::unique_ptr(
+ tracy::FileRead::Open(args.trace_file.data())
+ );
+ if (!f)
+ {
+ fprintf(stderr, "Could not open file %s\n", args.trace_file.data());
+ return 1;
+ }
+
+ auto worker = tracy::Worker(*f);
+
+ while (!worker.AreSourceLocationZonesReady())
+ {
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
+ }
+
+ auto& slz = worker.GetSourceLocationZones();
+ tracy::Vector slz_selected;
+ slz_selected.reserve(slz.size());
+
+ uint32_t total_cnt = 0;
+ for(auto it = slz.begin(); it != slz.end(); ++it)
+ {
+ if(it->second.total != 0)
+ {
+ ++total_cnt;
+ if(args.filter.empty())
+ {
+ slz_selected.push_back_no_space_check(it);
+ }
+ else
+ {
+ auto name = get_name(it->first, worker);
+ if(is_substring(args.filter, name, args.case_sensitive))
+ {
+ slz_selected.push_back_no_space_check(it);
+ }
+ }
+ }
+ }
+
+ std::vector columns;
+ if (args.unwrap)
+ {
+ columns = {
+ "name", "src_file", "src_line", "ns_since_start", "exec_time_ns"
+ };
+ }
+ else
+ {
+ columns = {
+ "name", "src_file", "src_line", "total_ns", "total_perc",
+ "counts", "mean_ns", "min_ns", "max_ns", "std_ns"
+ };
+ }
+ std::string header = join(columns, args.separator);
+ printf("%s\n", header.data());
+
+ const auto last_time = worker.GetLastTime();
+ for(auto& it : slz_selected)
+ {
+ std::vector values(columns.size());
+
+ values[0] = get_name(it->first, worker);
+
+ const auto& srcloc = worker.GetSourceLocation(it->first);
+ values[1] = worker.GetString(srcloc.file);
+ values[2] = std::to_string(srcloc.line);
+
+ const auto& zone_data = it->second;
+
+ if (args.unwrap)
+ {
+ int i = 0;
+ for (const auto& zone_thread_data : zone_data.zones) {
+ const auto zone_event = zone_thread_data.Zone();
+ const auto start = zone_event->Start();
+ const auto end = zone_event->End();
+
+ values[3] = std::to_string(start);
+
+ auto timespan = end - start;
+ if (args.self_time) {
+ timespan -= GetZoneChildTimeFast(worker, *zone_event);
+ }
+ values[4] = std::to_string(timespan);
+
+ std::string row = join(values, args.separator);
+ printf("%s\n", row.data());
+ }
+ }
+ else
+ {
+ const auto time = args.self_time ? zone_data.selfTotal : zone_data.total;
+ values[3] = std::to_string(time);
+ values[4] = std::to_string(100. * time / last_time);
+
+ values[5] = std::to_string(zone_data.zones.size());
+
+ const auto avg = (args.self_time ? zone_data.selfTotal : zone_data.total)
+ / zone_data.zones.size();
+ values[6] = std::to_string(avg);
+
+ const auto tmin = args.self_time ? zone_data.selfMin : zone_data.min;
+ const auto tmax = args.self_time ? zone_data.selfMax : zone_data.max;
+ values[7] = std::to_string(tmin);
+ values[8] = std::to_string(tmax);
+
+ const auto sz = zone_data.zones.size();
+ const auto ss = zone_data.sumSq
+ - 2. * zone_data.total * avg
+ + avg * avg * sz;
+ const auto std = sqrt(ss / (sz - 1));
+ values[9] = std::to_string(std);
+
+ std::string row = join(values, args.separator);
+ printf("%s\n", row.data());
+ }
+ }
+
+ return 0;
+}
diff --git a/csvexport/src/cxxopts.hpp b/csvexport/src/cxxopts.hpp
new file mode 100644
index 00000000..97381a96
--- /dev/null
+++ b/csvexport/src/cxxopts.hpp
@@ -0,0 +1,2197 @@
+/*
+
+Copyright (c) 2014, 2015, 2016, 2017 Jarryd Beck
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+*/
+
+#ifndef CXXOPTS_HPP_INCLUDED
+#define CXXOPTS_HPP_INCLUDED
+
+#include
+#include
+#include
+#include
+#include
+#include