[clang] [llvm] [TimeProfiler][Clang] Add zstd-compressed time-trace output support (PR #228627)

Chandler Carruth via cfe-commits cfe-commits at lists.llvm.org
Fri Oct 2 17:50:06 PDT 2026


https://github.com/chandlerc created https://github.com/llvm/llvm-project/pull/228627

Time trace JSON files can become very large (tens of megabytes) when profiling with fine granularity (-ftime-trace-granularity=0) or verbose output (-ftime-trace-verbose), while compressing by ~20x with zstd.

Add support for writing zstd-compressed time-trace output directly using LLVM's existing llvm::compression facilities:
- Extend llvm::timeTraceProfilerInitialize and llvm::timeTraceProfilerWrite to support DebugCompressionType and infer zstd compression from .zst/.zstd file extensions via llvm::inferTimeTraceCompressionFromPath.
- Add -ftime-trace-compress and -ftime-trace-compress=<none|zstd> driver and CC1 options to explicitly control compression regardless of output filename.
- Update default output filename inference when -ftime-trace is used without an explicit filename so compressed traces use a .json.zst extension.

Assisted-by: Antigravity with Gemini

>From ec262e8571bec9cea1e5d2aeeaf2ba4c7c55066c Mon Sep 17 00:00:00 2001
From: Chandler Carruth <chandlerc at gmail.com>
Date: Fri, 2 Oct 2026 18:06:45 +0000
Subject: [PATCH] [TimeProfiler][Clang] Add zstd-compressed time-trace output
 support

Time trace JSON files can become very large (tens of megabytes) when profiling
with fine granularity (-ftime-trace-granularity=0) or verbose output
(-ftime-trace-verbose), while compressing by ~20x with zstd.

Add support for writing zstd-compressed time-trace output directly using LLVM's
existing llvm::compression facilities:
- Extend llvm::timeTraceProfilerInitialize and llvm::timeTraceProfilerWrite to
  support DebugCompressionType and infer zstd compression from .zst/.zstd file
  extensions via llvm::inferTimeTraceCompressionFromPath.
- Add -ftime-trace-compress and -ftime-trace-compress=<none|zstd> driver and
  CC1 options to explicitly control compression regardless of output filename.
- Update default output filename inference when -ftime-trace is used without an
  explicit filename so compressed traces use a .json.zst extension.

Assisted-by: Antigravity with Gemini
---
 .../clang/Basic/DiagnosticDriverKinds.td      |  2 +
 .../include/clang/Frontend/FrontendOptions.h  |  5 ++
 clang/include/clang/Options/Options.td        | 11 +++
 clang/lib/Driver/Driver.cpp                   | 18 ++++-
 clang/lib/Driver/ToolChains/Clang.cpp         | 26 +++++++
 clang/lib/Frontend/CompilerInvocation.cpp     |  5 ++
 clang/test/Driver/compress-zstd.c             |  4 +
 clang/test/Driver/ftime-trace.cpp             | 11 ++-
 clang/tools/driver/cc1_main.cpp               |  7 +-
 llvm/include/llvm/Support/TimeProfiler.h      | 13 +++-
 llvm/lib/Support/TimeProfiler.cpp             | 74 +++++++++++++++++--
 llvm/unittests/Support/TimeProfilerTest.cpp   | 28 +++++++
 12 files changed, 186 insertions(+), 18 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td
index df6ff0c2cf399..369fbef8d0c85 100644
--- a/clang/include/clang/Basic/DiagnosticDriverKinds.td
+++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td
@@ -564,6 +564,8 @@ def warn_incompatible_sysroot : Warning<"using sysroot for '%0' but targeting '%
   InGroup<DiagGroup<"incompatible-sysroot">>;
 def warn_debug_compression_unavailable : Warning<"cannot compress debug sections (%0 not enabled)">,
   InGroup<DiagGroup<"debug-compression-unavailable">>;
+def err_drv_time_trace_compression_unavailable
+    : Error<"cannot compress time trace (%0 not enabled)">;
 def warn_drv_disabling_vptr_no_rtti_default : Warning<
   "implicitly disabling vptr sanitizer because rtti wasn't enabled">,
   InGroup<AutoDisableVptrSanitizer>;
diff --git a/clang/include/clang/Frontend/FrontendOptions.h b/clang/include/clang/Frontend/FrontendOptions.h
index b85cf4c007862..701c8d75fe61f 100644
--- a/clang/include/clang/Frontend/FrontendOptions.h
+++ b/clang/include/clang/Frontend/FrontendOptions.h
@@ -16,6 +16,7 @@
 #include "clang/Serialization/ModuleFileExtension.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/Compiler.h"
+#include "llvm/Support/Compression.h"
 #include "llvm/Support/MemoryBuffer.h"
 #include <cassert>
 #include <map>
@@ -543,6 +544,10 @@ class FrontendOptions {
   LLVM_PREFERRED_TYPE(bool)
   unsigned TimeTraceVerbose : 1;
 
+  /// Compression format for -ftime-trace output.
+  llvm::DebugCompressionType TimeTraceCompress =
+      llvm::DebugCompressionType::None;
+
   /// Path which stores the output files for -ftime-trace
   std::string TimeTracePath;
 
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index 60ae84cb0af5c..1d867223b9b68 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -4822,6 +4822,17 @@ def ftime_trace_verbose : Joined<["-"], "ftime-trace-verbose">, Group<f_Group>,
   HelpText<"Make time trace capture verbose event details (e.g. source filenames). This can increase the size of the output by 2-3 times">,
   Visibility<[ClangOption, CC1Option, CLOption, DXCOption]>,
   MarshallingInfoFlag<FrontendOpts<"TimeTraceVerbose">>;
+def ftime_trace_compress_EQ : Joined<["-"], "ftime-trace-compress=">, Group<f_Group>,
+  HelpText<"Compress -ftime-trace output with the specified algorithm">,
+  Values<"none,zstd">,
+  NormalizedValuesScope<"llvm::DebugCompressionType">,
+  NormalizedValues<["None", "Zstd"]>,
+  Visibility<[ClangOption, CC1Option, CLOption, DXCOption]>,
+  MarshallingInfoEnum<FrontendOpts<"TimeTraceCompress">, "None">;
+def ftime_trace_compress : Flag<["-"], "ftime-trace-compress">, Group<f_Group>,
+  Alias<ftime_trace_compress_EQ>, AliasArgs<["zstd"]>,
+  HelpText<"Alias for -ftime-trace-compress=zstd">,
+  Visibility<[ClangOption, CC1Option, CLOption, DXCOption]>;
 def ftime_trace_EQ : Joined<["-"], "ftime-trace=">, Group<f_Group>,
   HelpText<"Similar to -ftime-trace. Specify the JSON file or a directory which will contain the JSON file">,
   Visibility<[ClangOption, CC1Option, CLOption, DXCOption]>,
diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp
index 7649941a68b1c..79ad73d45825c 100644
--- a/clang/lib/Driver/Driver.cpp
+++ b/clang/lib/Driver/Driver.cpp
@@ -5173,6 +5173,16 @@ static void handleTimeTrace(Compilation &C, const ArgList &Args,
         /*CreatePrefixForHost=*/true);
   }
 
+  StringRef DefaultExt = ".json";
+  StringRef DefaultExtNoDot = "json";
+  if (const Arg *CompressArg =
+          Args.getLastArgNoClaim(options::OPT_ftime_trace_compress_EQ)) {
+    if (StringRef(CompressArg->getValue()) == "zstd") {
+      DefaultExt = ".json.zst";
+      DefaultExtNoDot = "json.zst";
+    }
+  }
+
   SmallString<128> Path;
   if (A->getOption().matches(options::OPT_ftime_trace_EQ)) {
     Path = A->getValue();
@@ -5181,7 +5191,7 @@ static void handleTimeTrace(Compilation &C, const ArgList &Args,
                                ? llvm::sys::path::stem(Result.getFilename())
                                : llvm::sys::path::stem(BaseInput));
       Tmp += OffloadingPrefix;
-      Tmp += ".json";
+      Tmp += DefaultExt;
       llvm::sys::path::append(Path, Tmp);
     }
   } else {
@@ -5191,7 +5201,7 @@ static void handleTimeTrace(Compilation &C, const ArgList &Args,
       Path = DumpDir->getValue();
       Path += llvm::sys::path::stem(BaseInput);
       Path += OffloadingPrefix;
-      Path += ".json";
+      Path += DefaultExt;
     } else if (!OffloadingPrefix.empty()) {
       // For offloading, derive path from -o output directory combined with
       // the input filename and offload prefix.
@@ -5200,10 +5210,10 @@ static void handleTimeTrace(Compilation &C, const ArgList &Args,
       if (Arg *FinalOutput = Args.getLastArg(options::OPT_o))
         Path = llvm::sys::path::parent_path(FinalOutput->getValue());
       llvm::sys::path::append(Path, TraceName);
-      Path += ".json";
+      Path += DefaultExt;
     } else {
       Path = Result.getFilename();
-      llvm::sys::path::replace_extension(Path, "json");
+      llvm::sys::path::replace_extension(Path, DefaultExtNoDot);
     }
   }
   const char *ResultFile = C.getArgs().MakeArgString(Path);
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index f0d04bf4b6a83..eda506494829a 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -46,11 +46,13 @@
 #include "llvm/ProfileData/InstrProfReader.h"
 #include "llvm/Support/CodeGen.h"
 #include "llvm/Support/Compiler.h"
+#include "llvm/Support/Compression.h"
 #include "llvm/Support/Error.h"
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/MathExtras.h"
 #include "llvm/Support/Path.h"
 #include "llvm/Support/Process.h"
+#include "llvm/Support/TimeProfiler.h"
 #include "llvm/Support/YAMLParser.h"
 #include "llvm/TargetParser/AArch64TargetParser.h"
 #include "llvm/TargetParser/ARMTargetParserCommon.h"
@@ -7344,6 +7346,28 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
     CmdArgs.push_back(Args.MakeArgString("-ftime-trace=" + Twine(Name)));
     Args.AddLastArg(CmdArgs, options::OPT_ftime_trace_granularity_EQ);
     Args.AddLastArg(CmdArgs, options::OPT_ftime_trace_verbose);
+    if (const Arg *CompressArg =
+            Args.getLastArg(options::OPT_ftime_trace_compress_EQ)) {
+      StringRef Value = CompressArg->getValue();
+      if (Value == "none") {
+        // Explicit -ftime-trace-compress=none disables compression even if the
+        // filename has a .zst/.zstd extension.
+      } else if (Value == "zstd") {
+        if (llvm::compression::zstd::isAvailable())
+          CmdArgs.push_back("-ftime-trace-compress=zstd");
+        else
+          D.Diag(diag::err_drv_time_trace_compression_unavailable) << "zstd";
+      } else {
+        D.Diag(diag::err_drv_unsupported_option_argument)
+            << CompressArg->getSpelling() << Value;
+      }
+    } else if (llvm::inferTimeTraceCompressionFromPath(Name) ==
+               llvm::DebugCompressionType::Zstd) {
+      if (llvm::compression::zstd::isAvailable())
+        CmdArgs.push_back("-ftime-trace-compress=zstd");
+      else
+        D.Diag(diag::err_drv_time_trace_compression_unavailable) << "zstd";
+    }
   }
 
   if (Arg *A = Args.getLastArg(options::OPT_ftrapv_handler_EQ)) {
@@ -9760,6 +9784,8 @@ void LinkerWrapper::ConstructJob(Compilation &C, const JobAction &JA,
       OPT_ftime_trace_EQ,
       OPT_ftime_trace_granularity_EQ,
       OPT_ftime_trace_verbose,
+      OPT_ftime_trace_compress,
+      OPT_ftime_trace_compress_EQ,
       OPT_opt_record_file,
       OPT_opt_record_format,
       OPT_opt_record_passes,
diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index b794a99c3d0c0..ed30d93b6026b 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -5239,6 +5239,11 @@ bool CompilerInvocation::CreateFromArgsImpl(
           llvm::driver::ProfileInstrKind::ProfileNone)
     Diags.Report(diag::err_drv_profile_instrument_use_path_with_no_kind);
 
+  if (Res.getFrontendOpts().TimeTraceCompress ==
+          llvm::DebugCompressionType::Zstd &&
+      !llvm::compression::zstd::isAvailable())
+    Diags.Report(diag::err_drv_time_trace_compression_unavailable) << "zstd";
+
   FixupInvocation(Res, Diags, Args, DashX);
 
   return Diags.getNumErrors() == NumErrorsBefore;
diff --git a/clang/test/Driver/compress-zstd.c b/clang/test/Driver/compress-zstd.c
index 84b26354589e9..a8e07631eede4 100644
--- a/clang/test/Driver/compress-zstd.c
+++ b/clang/test/Driver/compress-zstd.c
@@ -5,3 +5,7 @@
 
 // CHECK: {{"-cc1(as)?".* "--compress-debug-sections=zstd"}}
 // CHECK: "--compress-debug-sections=zstd"
+
+// RUN: %clang -### -c -ftime-trace -ftime-trace-compress %s -o a.o 2>&1 | FileCheck %s --check-prefix=TIME-TRACE-ZSTD
+// RUN: %clang -### -c -ftime-trace=a.json.zst %s -o a.o 2>&1 | FileCheck %s --check-prefix=TIME-TRACE-ZSTD
+// TIME-TRACE-ZSTD: "-cc1"{{.*}} "-ftime-trace=a.json.zst" "-ftime-trace-compress=zstd"
diff --git a/clang/test/Driver/ftime-trace.cpp b/clang/test/Driver/ftime-trace.cpp
index 60c5885704b58..77c46ae43bb53 100644
--- a/clang/test/Driver/ftime-trace.cpp
+++ b/clang/test/Driver/ftime-trace.cpp
@@ -55,14 +55,23 @@
 // LINK3: -cc1{{.*}} "-ftime-trace=e{{/|\\\\}}a-{{[^.]*}}.json" "-ftime-trace-granularity=0" "-ftime-trace-verbose"
 // LINK3: -cc1{{.*}} "-ftime-trace=e{{/|\\\\}}b-{{[^.]*}}.json" "-ftime-trace-granularity=0" "-ftime-trace-verbose"
 
-// RUN: %clang -### -ftime-trace -ftime-trace=e -ftime-trace-granularity=1 -ftime-trace-verbose -xassembler d/a.cpp 2>&1 | \
+// RUN: %clang -### -ftime-trace -ftime-trace=e -ftime-trace-granularity=1 -ftime-trace-verbose -ftime-trace-compress -ftime-trace-compress=none -xassembler d/a.cpp 2>&1 | \
 // RUN:   FileCheck %s --check-prefix=UNUSED
 // UNUSED:      warning: argument unused during compilation: '-ftime-trace'
 // UNUSED-NEXT: warning: argument unused during compilation: '-ftime-trace=e'
 // UNUSED-NEXT: warning: argument unused during compilation: '-ftime-trace-granularity=1'
 // UNUSED-NEXT: warning: argument unused during compilation: '-ftime-trace-verbose'
+// UNUSED-NEXT: warning: argument unused during compilation: '-ftime-trace-compress'
+// UNUSED-NEXT: warning: argument unused during compilation: '-ftime-trace-compress=none'
 // UNUSED-NOT:  warning:
 
+// RUN: %clang -### -c -ftime-trace=foo.json.zst -ftime-trace-compress=none -fintegrated-as d/a.cpp -o e/a.o 2>&1 | FileCheck %s --check-prefix=COMPRESS-NONE
+// COMPRESS-NONE: -cc1{{.*}} "-ftime-trace=foo.json.zst"
+// COMPRESS-NONE-NOT: "-ftime-trace-compress
+
+// RUN: not %clang -### -c -ftime-trace -ftime-trace-compress=invalid d/a.cpp -o e/a.o 2>&1 | FileCheck %s --check-prefix=COMPRESS-INVALID
+// COMPRESS-INVALID: error: unsupported argument 'invalid' to option '-ftime-trace-compress='
+
 template <typename T>
 struct Struct {
   T Num;
diff --git a/clang/tools/driver/cc1_main.cpp b/clang/tools/driver/cc1_main.cpp
index 89b0a340e6672..1c3fef0630ea4 100644
--- a/clang/tools/driver/cc1_main.cpp
+++ b/clang/tools/driver/cc1_main.cpp
@@ -253,7 +253,8 @@ int cc1_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
   if (!Clang->getFrontendOpts().TimeTracePath.empty()) {
     llvm::timeTraceProfilerInitialize(
         Clang->getFrontendOpts().TimeTraceGranularity, Argv0,
-        Clang->getFrontendOpts().TimeTraceVerbose);
+        Clang->getFrontendOpts().TimeTraceVerbose,
+        Clang->getFrontendOpts().TimeTraceCompress);
   }
   // --print-supported-cpus takes priority over the actual compilation.
   if (Clang->getFrontendOpts().PrintSupportedCPUs)
@@ -312,8 +313,10 @@ int cc1_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
   }
 
   if (llvm::timeTraceProfilerEnabled()) {
+    bool Binary = Clang->getFrontendOpts().TimeTraceCompress !=
+                  llvm::DebugCompressionType::None;
     if (auto profilerOutput = Clang->createOutputFile(
-            Clang->getFrontendOpts().TimeTracePath, /*Binary=*/false,
+            Clang->getFrontendOpts().TimeTracePath, Binary,
             /*RemoveFileOnSignal=*/false,
             /*useTemporary=*/false)) {
       llvm::timeTraceProfilerWrite(*profilerOutput);
diff --git a/llvm/include/llvm/Support/TimeProfiler.h b/llvm/include/llvm/Support/TimeProfiler.h
index 2eb2c09d1becd..417918cb1d5ae 100644
--- a/llvm/include/llvm/Support/TimeProfiler.h
+++ b/llvm/include/llvm/Support/TimeProfiler.h
@@ -78,6 +78,7 @@
 
 #include "llvm/ADT/STLFunctionalExtras.h"
 #include "llvm/Support/Compiler.h"
+#include "llvm/Support/Compression.h"
 #include "llvm/Support/Error.h"
 
 namespace llvm {
@@ -113,12 +114,18 @@ LLVM_ABI bool isTimeTraceVerbose();
 
 struct TimeTraceProfilerEntry;
 
+/// Infer the time trace compression format from \p Path based on its file
+/// extension (.zst/.zstd -> Zstd, otherwise None).
+LLVM_ABI DebugCompressionType
+inferTimeTraceCompressionFromPath(StringRef Path);
+
 /// Initialize the time trace profiler.
 /// This sets up the global \p TimeTraceProfilerInstance
 /// variable to be the profiler instance.
-LLVM_ABI void timeTraceProfilerInitialize(unsigned TimeTraceGranularity,
-                                          StringRef ProcName,
-                                          bool TimeTraceVerbose = false);
+LLVM_ABI void timeTraceProfilerInitialize(
+    unsigned TimeTraceGranularity, StringRef ProcName,
+    bool TimeTraceVerbose = false,
+    DebugCompressionType TimeTraceCompress = DebugCompressionType::None);
 
 /// Cleanup the time trace profiler, if it was initialized.
 LLVM_ABI void timeTraceProfilerCleanup();
diff --git a/llvm/lib/Support/TimeProfiler.cpp b/llvm/lib/Support/TimeProfiler.cpp
index 002529f20d661..c2816e98cd30a 100644
--- a/llvm/lib/Support/TimeProfiler.cpp
+++ b/llvm/lib/Support/TimeProfiler.cpp
@@ -127,12 +127,15 @@ struct InProgressEntry {
 };
 
 struct llvm::TimeTraceProfiler {
-  TimeTraceProfiler(unsigned TimeTraceGranularity = 0, StringRef ProcName = "",
-                    bool TimeTraceVerbose = false)
+  TimeTraceProfiler(
+      unsigned TimeTraceGranularity = 0, StringRef ProcName = "",
+      bool TimeTraceVerbose = false,
+      DebugCompressionType TimeTraceCompress = DebugCompressionType::None)
       : BeginningOfTime(system_clock::now()), StartTime(ClockType::now()),
         ProcName(ProcName), Pid(sys::Process::getProcessId()),
         Tid(llvm::get_threadid()), TimeTraceGranularity(TimeTraceGranularity),
-        TimeTraceVerbose(TimeTraceVerbose) {
+        TimeTraceVerbose(TimeTraceVerbose),
+        TimeTraceCompress(TimeTraceCompress) {
     llvm::get_thread_name(ThreadName);
   }
 
@@ -212,7 +215,7 @@ struct llvm::TimeTraceProfiler {
 
   // Write events from this TimeTraceProfilerInstance and
   // ThreadTimeTraceProfilerInstances.
-  void write(raw_pwrite_stream &OS) {
+  void write(raw_ostream &OS) {
     // Acquire Mutex as reading ThreadTimeTraceProfilerInstances.
     auto &Instances = getTimeTraceProfilerInstances();
     std::lock_guard<std::mutex> Lock(Instances.Lock);
@@ -383,6 +386,9 @@ struct llvm::TimeTraceProfiler {
   // Make time trace capture verbose event details (e.g. source filenames). This
   // can increase the size of the output by 2-3 times.
   const bool TimeTraceVerbose;
+
+  // Optional compression format for output.
+  DebugCompressionType TimeTraceCompress;
 };
 
 bool llvm::isTimeTraceVerbose() {
@@ -390,14 +396,22 @@ bool llvm::isTimeTraceVerbose() {
          getTimeTraceProfilerInstance()->TimeTraceVerbose;
 }
 
+DebugCompressionType llvm::inferTimeTraceCompressionFromPath(StringRef Path) {
+  StringRef Ext = llvm::sys::path::extension(Path);
+  if (Ext.equals_insensitive(".zst") || Ext.equals_insensitive(".zstd"))
+    return DebugCompressionType::Zstd;
+  return DebugCompressionType::None;
+}
+
 void llvm::timeTraceProfilerInitialize(unsigned TimeTraceGranularity,
                                        StringRef ProcName,
-                                       bool TimeTraceVerbose) {
+                                       bool TimeTraceVerbose,
+                                       DebugCompressionType TimeTraceCompress) {
   assert(TimeTraceProfilerInstance == nullptr &&
          "Profiler should not be initialized");
   TimeTraceProfilerInstance = new TimeTraceProfiler(
       TimeTraceGranularity, llvm::sys::path::filename(ProcName),
-      TimeTraceVerbose);
+      TimeTraceVerbose, TimeTraceCompress);
 }
 
 // Removes all TimeTraceProfilerInstances.
@@ -425,7 +439,30 @@ void llvm::timeTraceProfilerFinishThread() {
 void llvm::timeTraceProfilerWrite(raw_pwrite_stream &OS) {
   assert(TimeTraceProfilerInstance != nullptr &&
          "Profiler object can't be null");
-  TimeTraceProfilerInstance->write(OS);
+  DebugCompressionType CompressType =
+      TimeTraceProfilerInstance->TimeTraceCompress;
+  if (CompressType == DebugCompressionType::None) {
+    TimeTraceProfilerInstance->write(OS);
+    return;
+  }
+
+  compression::Format F = compression::formatFor(CompressType);
+  if (const char *Reason = compression::getReasonIfUnsupported(F))
+    report_fatal_error(Reason);
+
+  SmallVector<char, 0> Uncompressed;
+  {
+    raw_svector_ostream UncompressedOS(Uncompressed);
+    TimeTraceProfilerInstance->write(UncompressedOS);
+  }
+  SmallVector<uint8_t, 0> Compressed;
+  compression::compress(
+      compression::Params(F),
+      ArrayRef(reinterpret_cast<const uint8_t *>(Uncompressed.data()),
+               Uncompressed.size()),
+      Compressed);
+  OS.write(reinterpret_cast<const char *>(Compressed.data()),
+           Compressed.size());
 }
 
 Error llvm::timeTraceProfilerWrite(StringRef PreferredFileName,
@@ -437,14 +474,35 @@ Error llvm::timeTraceProfilerWrite(StringRef PreferredFileName,
   if (Path.empty()) {
     Path = FallbackFileName == "-" ? "out" : FallbackFileName.str();
     Path += TimeTraceFileExtension;
+    if (TimeTraceProfilerInstance->TimeTraceCompress ==
+        DebugCompressionType::Zstd)
+      Path += ".zst";
+  }
+
+  DebugCompressionType CompressType =
+      TimeTraceProfilerInstance->TimeTraceCompress;
+  if (CompressType == DebugCompressionType::None)
+    CompressType = inferTimeTraceCompressionFromPath(Path);
+
+  if (CompressType != DebugCompressionType::None) {
+    if (const char *Reason = compression::getReasonIfUnsupported(
+            compression::formatFor(CompressType)))
+      return createStringError(inconvertibleErrorCode(), Reason);
   }
 
   std::error_code EC;
-  raw_fd_ostream OS(Path, EC, sys::fs::OF_TextWithCRLF);
+  sys::fs::OpenFlags Flags = CompressType == DebugCompressionType::None
+                                 ? sys::fs::OF_TextWithCRLF
+                                 : sys::fs::OF_None;
+  raw_fd_ostream OS(Path, EC, Flags);
   if (EC)
     return createStringError(EC, "Could not open " + Path);
 
+  DebugCompressionType SavedCompress =
+      TimeTraceProfilerInstance->TimeTraceCompress;
+  TimeTraceProfilerInstance->TimeTraceCompress = CompressType;
   timeTraceProfilerWrite(OS);
+  TimeTraceProfilerInstance->TimeTraceCompress = SavedCompress;
   return Error::success();
 }
 
diff --git a/llvm/unittests/Support/TimeProfilerTest.cpp b/llvm/unittests/Support/TimeProfilerTest.cpp
index aa1185bae2961..38d25b35991aa 100644
--- a/llvm/unittests/Support/TimeProfilerTest.cpp
+++ b/llvm/unittests/Support/TimeProfilerTest.cpp
@@ -43,6 +43,34 @@ TEST(TimeProfiler, Scope_Smoke) {
   ASSERT_TRUE(json.find(R"("detail":"detail")") != std::string::npos);
 }
 
+TEST(TimeProfiler, Compression) {
+  EXPECT_EQ(inferTimeTraceCompressionFromPath("foo.json"),
+            DebugCompressionType::None);
+  EXPECT_EQ(inferTimeTraceCompressionFromPath("foo.json.zst"),
+            DebugCompressionType::Zstd);
+  EXPECT_EQ(inferTimeTraceCompressionFromPath("foo.ZSTD"),
+            DebugCompressionType::Zstd);
+
+  if (!compression::zstd::isAvailable())
+    return;
+
+  timeTraceProfilerInitialize(/*TimeTraceGranularity=*/0, "test",
+                              /*TimeTraceVerbose=*/false,
+                              DebugCompressionType::Zstd);
+  {
+    TimeTraceScope Scope("compressed_event", "compressed_detail");
+  }
+
+  SmallVector<char, 0> CompressedChars;
+  raw_svector_ostream OS(CompressedChars);
+  timeTraceProfilerWrite(OS);
+  timeTraceProfilerCleanup();
+
+  ASSERT_FALSE(CompressedChars.empty());
+  // Compressed output must not start with '{'.
+  EXPECT_NE(CompressedChars.front(), '{');
+}
+
 TEST(TimeProfiler, Begin_End_Smoke) {
   setupProfiler();
 



More information about the cfe-commits mailing list