1
#include "source/extensions/watchdog/profile_action/profile_action.h"
2

            
3
#include <chrono>
4

            
5
#include "envoy/thread/thread.h"
6

            
7
#include "source/common/profiler/profiler.h"
8
#include "source/common/protobuf/utility.h"
9
#include "source/common/stats/symbol_table.h"
10

            
11
#include "absl/strings/str_format.h"
12

            
13
namespace Envoy {
14
namespace Extensions {
15
namespace Watchdog {
16
namespace ProfileAction {
17
namespace {
18
static constexpr uint64_t DefaultMaxProfiles = 10;
19

            
20
6
std::string generateProfileFilePath(const std::string& directory, TimeSource& time_source) {
21
6
  const uint64_t timestamp = DateUtil::nowToSeconds(time_source);
22
6
  if (absl::EndsWith(directory, "/")) {
23
    return absl::StrFormat("%s%s.%d", directory, "ProfileAction", timestamp);
24
  }
25
6
  return absl::StrFormat("%s/%s.%d", directory, "ProfileAction", timestamp);
26
6
}
27
} // namespace
28

            
29
ProfileAction::ProfileAction(
30
    envoy::extensions::watchdog::profile_action::v3::ProfileActionConfig& config,
31
    Server::Configuration::GuardDogActionFactoryContext& context)
32
8
    : path_(config.profile_path()),
33
      duration_(
34
8
          std::chrono::milliseconds(PROTOBUF_GET_MS_OR_DEFAULT(config, profile_duration, 5000))),
35
8
      max_profiles_(config.max_profiles() == 0 ? DefaultMaxProfiles : config.max_profiles()),
36
8
      profiles_attempted_(context.stats_.counterFromStatName(
37
8
          Stats::StatNameManagedStorage(
38
8
              absl::StrCat(context.guarddog_name_, ".profile_action.attempted"),
39
8
              context.stats_.symbolTable())
40
8
              .statName())),
41
8
      profiles_successfully_captured_(context.stats_.counterFromStatName(
42
8
          Stats::StatNameManagedStorage(
43
8
              absl::StrCat(context.guarddog_name_, ".profile_action.successfully_captured"),
44
8
              context.stats_.symbolTable())
45
8
              .statName())),
46
8
      context_(context), timer_cb_(context_.dispatcher_.createTimer([this] {
47
6
        if (Profiler::Cpu::profilerEnabled()) {
48
6
          Profiler::Cpu::stopProfiler();
49
6
          running_profile_ = false;
50
6
        } else {
51
          ENVOY_LOG_MISC(error,
52
                         "Profile Action's stop() was scheduled, but profiler isn't running!");
53
          return;
54
        }
55

            
56
6
        if (!context_.api_.fileSystem().fileExists(profile_filename_)) {
57
          ENVOY_LOG_MISC(error, "Profile file {} wasn't created!", profile_filename_);
58
6
        } else {
59
6
          profiles_successfully_captured_.inc();
60
6
        }
61
8
      })) {}
62

            
63
void ProfileAction::run(
64
    envoy::config::bootstrap::v3::Watchdog::WatchdogAction::WatchdogEvent /*event*/,
65
    const std::vector<std::pair<Thread::ThreadId, MonotonicTime>>& thread_last_checkin_pairs,
66
11
    MonotonicTime /*now*/) {
67
11
  if (running_profile_) {
68
1
    return;
69
1
  }
70
10
  profiles_attempted_.inc();
71

            
72
  // Check if there's a tid that justifies profiling
73
10
  if (thread_last_checkin_pairs.empty()) {
74
2
    ENVOY_LOG_MISC(warn, "Profile Action: No tids were provided.");
75
2
    return;
76
2
  }
77

            
78
8
  if (profiles_started_ >= max_profiles_) {
79
1
    ENVOY_LOG_MISC(warn,
80
1
                   "Profile Action: Unable to profile: enabled but already wrote {} profiles.",
81
1
                   profiles_started_);
82
1
    return;
83
1
  }
84

            
85
7
  auto& fs = context_.api_.fileSystem();
86
7
  if (!fs.directoryExists(path_)) {
87
1
    ENVOY_LOG_MISC(error, "Profile Action: Directory path {} doesn't exist.", path_);
88
1
    return;
89
1
  }
90

            
91
  // Generate file path for output and try to profile
92
6
  profile_filename_ = generateProfileFilePath(path_, context_.api_.timeSource());
93

            
94
6
  if (!Profiler::Cpu::profilerEnabled()) {
95
6
    if (Profiler::Cpu::startProfiler(profile_filename_)) {
96
      // Update state
97
6
      running_profile_ = true;
98
6
      ++profiles_started_;
99

            
100
      // Schedule callback to stop
101
6
      timer_cb_->enableTimer(duration_);
102
6
    } else {
103
      ENVOY_LOG_MISC(error, "Profile Action failed to start the profiler.");
104
    }
105
6
  } else {
106
    ENVOY_LOG_MISC(error, "Profile Action unable to start the profiler as it is in use elsewhere.");
107
  }
108
6
}
109

            
110
} // namespace ProfileAction
111
} // namespace Watchdog
112
} // namespace Extensions
113
} // namespace Envoy