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

via cfe-commits cfe-commits at lists.llvm.org
Sun Oct 18 03:46:20 PDT 2026


Author: Chandler Carruth
Date: 2026-10-18T10:46:14Z
New Revision: 33632ffa14254f6acea12afc368e99e49ab695e2

URL: https://github.com/llvm/llvm-project/commit/33632ffa14254f6acea12afc368e99e49ab695e2
DIFF: https://github.com/llvm/llvm-project/commit/33632ffa14254f6acea12afc368e99e49ab695e2.diff

LOG: [TimeProfiler][Clang] Add zstd-compressed time-trace output support (#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:
- Introduce llvm::TimeTraceCompression ({Infer, None, Zstd}) in
TimeProfiler.h and extend llvm::timeTraceProfilerInitialize and
llvm::timeTraceProfilerWrite to infer zstd compression from .zst/.zstd
file extensions via llvm::inferTimeTraceCompressionFromPath when set to
Infer.
- Add -ftime-trace-compress=<none|zstd|infer> driver and CC1 option to
explicitly control compression or infer it from the 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

Added: 
    

Modified: 
    clang/include/clang/Basic/DiagnosticDriverKinds.td
    clang/include/clang/Frontend/FrontendOptions.h
    clang/include/clang/Options/Options.td
    clang/lib/Driver/Driver.cpp
    clang/lib/Driver/ToolChains/Clang.cpp
    clang/lib/Frontend/CompilerInvocation.cpp
    clang/test/Driver/ftime-trace.cpp
    clang/tools/driver/cc1_main.cpp
    llvm/include/llvm/Support/TimeProfiler.h
    llvm/lib/Support/TimeProfiler.cpp
    llvm/unittests/Support/TimeProfilerTest.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td
index b5b1ac744b49de..79859e724a8179 100644
--- a/clang/include/clang/Basic/DiagnosticDriverKinds.td
+++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td
@@ -563,6 +563,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 7e2b0936525662..087a96a13af3f0 100644
--- a/clang/include/clang/Frontend/FrontendOptions.h
+++ b/clang/include/clang/Frontend/FrontendOptions.h
@@ -17,6 +17,7 @@
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/Compiler.h"
 #include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/TimeProfiler.h"
 #include <cassert>
 #include <map>
 #include <memory>
@@ -549,6 +550,10 @@ class FrontendOptions {
   LLVM_PREFERRED_TYPE(bool)
   unsigned TimeTraceVerbose : 1;
 
+  /// Compression mode for -ftime-trace output.
+  llvm::TimeTraceCompression TimeTraceCompress =
+      llvm::TimeTraceCompression::Infer;
+
   /// 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 41afddd0ffe82e..38d285b8268e15 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -4848,6 +4848,13 @@ 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,infer">,
+  NormalizedValuesScope<"llvm::TimeTraceCompression">,
+  NormalizedValues<["None", "Zstd", "Infer"]>,
+  Visibility<[ClangOption, CC1Option, CLOption, DXCOption]>,
+  MarshallingInfoEnum<FrontendOpts<"TimeTraceCompress">, "Infer">;
 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 b2dbc32f1b89fd..b9c9fee18e9c3e 100644
--- a/clang/lib/Driver/Driver.cpp
+++ b/clang/lib/Driver/Driver.cpp
@@ -5181,6 +5181,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();
@@ -5189,7 +5199,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 {
@@ -5199,7 +5209,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.
@@ -5208,10 +5218,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 45aede75681103..608d7bb68a4e6d 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -7390,6 +7390,7 @@ 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);
+    Args.AddLastArg(CmdArgs, options::OPT_ftime_trace_compress_EQ);
   }
 
   if (Arg *A = Args.getLastArg(options::OPT_ftrapv_handler_EQ)) {
@@ -9812,6 +9813,7 @@ 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_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 c9281c2a6d6b41..225962359129b3 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -5246,6 +5246,16 @@ bool CompilerInvocation::CreateFromArgsImpl(
           llvm::driver::ProfileInstrKind::ProfileNone)
     Diags.Report(diag::err_drv_profile_instrument_use_path_with_no_kind);
 
+  llvm::TimeTraceCompression TimeTraceCompress =
+      Res.getFrontendOpts().TimeTraceCompress;
+  if (TimeTraceCompress == llvm::TimeTraceCompression::Infer &&
+      !Res.getFrontendOpts().TimeTracePath.empty())
+    TimeTraceCompress = llvm::inferTimeTraceCompressionFromPath(
+        Res.getFrontendOpts().TimeTracePath);
+  if (TimeTraceCompress == llvm::TimeTraceCompression::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/ftime-trace.cpp b/clang/test/Driver/ftime-trace.cpp
index 60c5885704b58e..4170e00fd408ef 100644
--- a/clang/test/Driver/ftime-trace.cpp
+++ b/clang/test/Driver/ftime-trace.cpp
@@ -55,14 +55,30 @@
 // 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=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=none'
 // UNUSED-NOT:  warning:
 
+// RUN: %clang -### -c -ftime-trace -ftime-trace-compress=zstd -fintegrated-as d/a.cpp -o e/a.o 2>&1 | FileCheck %s --check-prefix=COMPRESS-ZSTD
+// COMPRESS-ZSTD: -cc1{{.*}} "-ftime-trace=e/a.json.zst" "-ftime-trace-compress=zstd"
+
+// RUN: %clang -### -c -ftime-trace -ftime-trace-compress=zstd -ftime-trace-compress=none -fintegrated-as d/a.cpp -o e/a.o 2>&1 | FileCheck %s --check-prefix=COMPRESS-OVERRIDE-NONE
+// COMPRESS-OVERRIDE-NONE: -cc1{{.*}} "-ftime-trace=e/a.json" "-ftime-trace-compress=none"
+
+// RUN: %clang -### -c -ftime-trace -ftime-trace-compress=zstd -ftime-trace-compress=infer -fintegrated-as d/a.cpp -o e/a.o 2>&1 | FileCheck %s --check-prefix=COMPRESS-OVERRIDE-INFER
+// COMPRESS-OVERRIDE-INFER: -cc1{{.*}} "-ftime-trace=e/a.json" "-ftime-trace-compress=infer"
+
+// 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" "-ftime-trace-compress=none"
+
+// RUN: not %clangxx -S -no-canonical-prefixes -ftime-trace -ftime-trace-compress=invalid -o out %s 2>&1 | FileCheck %s --check-prefix=COMPRESS-INVALID
+// COMPRESS-INVALID: error: invalid value 'invalid' in '-ftime-trace-compress=invalid'
+
 template <typename T>
 struct Struct {
   T Num;

diff  --git a/clang/tools/driver/cc1_main.cpp b/clang/tools/driver/cc1_main.cpp
index 89b0a340e6672e..5ed0c372717e1d 100644
--- a/clang/tools/driver/cc1_main.cpp
+++ b/clang/tools/driver/cc1_main.cpp
@@ -251,9 +251,14 @@ int cc1_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
                                                   std::move(PCHOps));
 
   if (!Clang->getFrontendOpts().TimeTracePath.empty()) {
+    llvm::TimeTraceCompression Compress =
+        Clang->getFrontendOpts().TimeTraceCompress;
+    if (Compress == llvm::TimeTraceCompression::Infer)
+      Compress = llvm::inferTimeTraceCompressionFromPath(
+          Clang->getFrontendOpts().TimeTracePath);
     llvm::timeTraceProfilerInitialize(
         Clang->getFrontendOpts().TimeTraceGranularity, Argv0,
-        Clang->getFrontendOpts().TimeTraceVerbose);
+        Clang->getFrontendOpts().TimeTraceVerbose, Compress);
   }
   // --print-supported-cpus takes priority over the actual compilation.
   if (Clang->getFrontendOpts().PrintSupportedCPUs)
@@ -312,8 +317,14 @@ int cc1_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
   }
 
   if (llvm::timeTraceProfilerEnabled()) {
+    llvm::TimeTraceCompression Compress =
+        Clang->getFrontendOpts().TimeTraceCompress;
+    if (Compress == llvm::TimeTraceCompression::Infer)
+      Compress = llvm::inferTimeTraceCompressionFromPath(
+          Clang->getFrontendOpts().TimeTracePath);
+    bool Binary = Compress == llvm::TimeTraceCompression::Zstd;
     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 e80ace27a23ca8..3119784a1e1afd 100644
--- a/llvm/include/llvm/Support/TimeProfiler.h
+++ b/llvm/include/llvm/Support/TimeProfiler.h
@@ -97,6 +97,13 @@ enum class TimeTraceEventType {
   AsyncEvent
 };
 
+/// Compression mode for time trace output.
+enum class TimeTraceCompression {
+  Infer,
+  None,
+  Zstd,
+};
+
 struct TimeTraceMetadata {
   std::string Detail;
   // Source file and line number information for the event.
@@ -113,12 +120,17 @@ 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 TimeTraceCompression 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,
+    TimeTraceCompression TimeTraceCompress = TimeTraceCompression::Infer);
 
 /// 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 002529f20d661b..05df72e30ad1a3 100644
--- a/llvm/lib/Support/TimeProfiler.cpp
+++ b/llvm/lib/Support/TimeProfiler.cpp
@@ -15,6 +15,7 @@
 #include "llvm/ADT/STLFunctionalExtras.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringMap.h"
+#include "llvm/Support/Compression.h"
 #include "llvm/Support/JSON.h"
 #include "llvm/Support/Path.h"
 #include "llvm/Support/Process.h"
@@ -127,12 +128,15 @@ struct InProgressEntry {
 };
 
 struct llvm::TimeTraceProfiler {
-  TimeTraceProfiler(unsigned TimeTraceGranularity = 0, StringRef ProcName = "",
-                    bool TimeTraceVerbose = false)
+  TimeTraceProfiler(
+      unsigned TimeTraceGranularity = 0, StringRef ProcName = "",
+      bool TimeTraceVerbose = false,
+      TimeTraceCompression TimeTraceCompress = TimeTraceCompression::Infer)
       : 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 +216,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 +387,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;
+
+  // Compression mode for output.
+  TimeTraceCompression TimeTraceCompress;
 };
 
 bool llvm::isTimeTraceVerbose() {
@@ -390,14 +397,22 @@ bool llvm::isTimeTraceVerbose() {
          getTimeTraceProfilerInstance()->TimeTraceVerbose;
 }
 
+TimeTraceCompression llvm::inferTimeTraceCompressionFromPath(StringRef Path) {
+  StringRef Ext = llvm::sys::path::extension(Path);
+  if (Ext.equals_insensitive(".zst") || Ext.equals_insensitive(".zstd"))
+    return TimeTraceCompression::Zstd;
+  return TimeTraceCompression::None;
+}
+
 void llvm::timeTraceProfilerInitialize(unsigned TimeTraceGranularity,
                                        StringRef ProcName,
-                                       bool TimeTraceVerbose) {
+                                       bool TimeTraceVerbose,
+                                       TimeTraceCompression 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 +440,30 @@ void llvm::timeTraceProfilerFinishThread() {
 void llvm::timeTraceProfilerWrite(raw_pwrite_stream &OS) {
   assert(TimeTraceProfilerInstance != nullptr &&
          "Profiler object can't be null");
-  TimeTraceProfilerInstance->write(OS);
+  TimeTraceCompression CompressType =
+      TimeTraceProfilerInstance->TimeTraceCompress;
+  if (CompressType != TimeTraceCompression::Zstd) {
+    TimeTraceProfilerInstance->write(OS);
+    return;
+  }
+
+  compression::Format F = compression::Format::Zstd;
+  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,
@@ -433,18 +471,37 @@ Error llvm::timeTraceProfilerWrite(StringRef PreferredFileName,
   assert(TimeTraceProfilerInstance != nullptr &&
          "Profiler object can't be null");
 
+  TimeTraceCompression CompressType =
+      TimeTraceProfilerInstance->TimeTraceCompress;
   std::string Path = PreferredFileName.str();
   if (Path.empty()) {
     Path = FallbackFileName == "-" ? "out" : FallbackFileName.str();
     Path += TimeTraceFileExtension;
+    if (CompressType == TimeTraceCompression::Zstd)
+      Path += ".zst";
+  } else if (CompressType == TimeTraceCompression::Infer) {
+    CompressType = inferTimeTraceCompressionFromPath(Path);
+  }
+
+  if (CompressType == TimeTraceCompression::Zstd) {
+    if (const char *Reason =
+            compression::getReasonIfUnsupported(compression::Format::Zstd))
+      return createStringError(inconvertibleErrorCode(), Reason);
   }
 
   std::error_code EC;
-  raw_fd_ostream OS(Path, EC, sys::fs::OF_TextWithCRLF);
+  sys::fs::OpenFlags Flags = CompressType == TimeTraceCompression::Zstd
+                                 ? sys::fs::OF_None
+                                 : sys::fs::OF_TextWithCRLF;
+  raw_fd_ostream OS(Path, EC, Flags);
   if (EC)
     return createStringError(EC, "Could not open " + Path);
 
+  TimeTraceCompression 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 aa1185bae2961f..d10b4bce2c5fca 100644
--- a/llvm/unittests/Support/TimeProfilerTest.cpp
+++ b/llvm/unittests/Support/TimeProfilerTest.cpp
@@ -15,6 +15,10 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/Support/TimeProfiler.h"
+#include "llvm/ADT/ScopeExit.h"
+#include "llvm/Support/Compression.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/MemoryBuffer.h"
 #include "gtest/gtest.h"
 
 using namespace llvm;
@@ -43,6 +47,64 @@ TEST(TimeProfiler, Scope_Smoke) {
   ASSERT_TRUE(json.find(R"("detail":"detail")") != std::string::npos);
 }
 
+TEST(TimeProfiler, Compression) {
+  EXPECT_EQ(inferTimeTraceCompressionFromPath("foo.json"),
+            TimeTraceCompression::None);
+  EXPECT_EQ(inferTimeTraceCompressionFromPath("foo.json.zst"),
+            TimeTraceCompression::Zstd);
+  EXPECT_EQ(inferTimeTraceCompressionFromPath("foo.ZSTD"),
+            TimeTraceCompression::Zstd);
+
+  if (!compression::zstd::isAvailable())
+    return;
+
+  timeTraceProfilerInitialize(/*TimeTraceGranularity=*/0, "test",
+                              /*TimeTraceVerbose=*/false,
+                              TimeTraceCompression::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(), '{');
+
+  SmallString<128> TempPath;
+  sys::fs::createUniquePath("time-trace-%%%%%%%.json.zst", TempPath, true);
+  llvm::scope_exit CleanupFile([&]() { sys::fs::remove(TempPath); });
+
+  // Default (TimeTraceCompression::Infer) infers Zstd from the .zst extension.
+  timeTraceProfilerInitialize(/*TimeTraceGranularity=*/0, "test");
+  {
+    TimeTraceScope Scope("inferred_event", "inferred_detail");
+  }
+  ASSERT_FALSE(errorToBool(timeTraceProfilerWrite(TempPath, "fallback")));
+  timeTraceProfilerCleanup();
+  auto BufOrErr = MemoryBuffer::getFile(TempPath);
+  ASSERT_TRUE(static_cast<bool>(BufOrErr));
+  ASSERT_FALSE((*BufOrErr)->getBuffer().empty());
+  EXPECT_NE((*BufOrErr)->getBuffer().front(), '{');
+
+  // Explicit TimeTraceCompression::None overrides the .zst extension.
+  timeTraceProfilerInitialize(/*TimeTraceGranularity=*/0, "test",
+                              /*TimeTraceVerbose=*/false,
+                              TimeTraceCompression::None);
+  {
+    TimeTraceScope Scope("uncompressed_event", "uncompressed_detail");
+  }
+  ASSERT_FALSE(errorToBool(timeTraceProfilerWrite(TempPath, "fallback")));
+  timeTraceProfilerCleanup();
+  BufOrErr = MemoryBuffer::getFile(TempPath);
+  ASSERT_TRUE(static_cast<bool>(BufOrErr));
+  ASSERT_FALSE((*BufOrErr)->getBuffer().empty());
+  EXPECT_EQ((*BufOrErr)->getBuffer().front(), '{');
+}
+
 TEST(TimeProfiler, Begin_End_Smoke) {
   setupProfiler();
 


        


More information about the cfe-commits mailing list