Apache Mesos
osx.hpp
Go to the documentation of this file.
1 // Licensed under the Apache License, Version 2.0 (the "License");
2 // you may not use this file except in compliance with the License.
3 // You may obtain a copy of the License at
4 //
5 // http://www.apache.org/licenses/LICENSE-2.0
6 //
7 // Unless required by applicable law or agreed to in writing, software
8 // distributed under the License is distributed on an "AS IS" BASIS,
9 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 // See the License for the specific language governing permissions and
11 // limitations under the License.
12 
13 #ifndef __STOUT_OS_OSX_HPP__
14 #define __STOUT_OS_OSX_HPP__
15 
16 // This file contains OSX-only OS utilities.
17 #ifndef __APPLE__
18 #error "stout/os/osx.hpp is only available on OSX systems."
19 #endif
20 
21 #include <libproc.h>
22 
23 #include <sys/sysctl.h>
24 #include <sys/types.h> // For pid_t.
25 
26 #include <queue>
27 #include <set>
28 #include <string>
29 #include <vector>
30 
31 #include <stout/error.hpp>
32 #include <stout/none.hpp>
33 #include <stout/strings.hpp>
34 
35 #include <stout/os/os.hpp>
36 #include <stout/os/pagesize.hpp>
37 #include <stout/os/process.hpp>
38 #include <stout/os/sysctl.hpp>
39 
40 namespace os {
41 
42 inline Result<Process> process(pid_t pid)
43 {
45  os::sysctl(CTL_KERN, KERN_PROC, KERN_PROC_PID, pid).table(1);
46 
47  if (processes.isError()) {
48  return Error("Failed to get process via sysctl: " + processes.error());
49  } else if (processes->size() != 1) {
50  return None();
51  }
52 
53  const kinfo_proc process = processes.get()[0];
54 
55  // The command line from 'process.kp_proc.p_comm' only includes the
56  // first 16 characters from "arg0" (i.e., the canonical executable
57  // name). We can try to get "argv" via some sysctl magic. This first
58  // requires determining "argc" via KERN_PROCARGS2 followed by the
59  // actual arguments via KERN_PROCARGS. This is still insufficient
60  // with insufficient privilege (e.g., not being root). If we were
61  // only interested in the "executable path" (i.e., the first
62  // argument to 'exec' but none of the arguments) we could use
63  // proc_pidpath() instead.
64  Option<std::string> command = None();
65 
66  // Alternative implemenations using KERN_PROCARGS2:
67  // https://bitbucket.org/flub/psi-win32/src/d38f288de3b8/src/arch/macosx/macosx_process.c#cl-552
68  // https://gist.github.com/nonowarn/770696
69 
70 #ifdef KERN_PROCARGS2
71  // Looking at the source code of XNU (the Darwin kernel for OS X:
72  // www.opensource.apple.com/source/xnu/xnu-1699.24.23/bsd/kern/kern_sysctl.c),
73  // it appears as though KERN_PROCARGS2 writes 'argc' as the first
74  // word of the returned bytes.
75  Try<std::string> args = os::sysctl(CTL_KERN, KERN_PROCARGS2, pid).string();
76 
77  if (args.isSome()) {
78  int argc = *((int*) args->data());
79 
80  if (argc > 0) {
81  // Now grab the arguments.
82  args = os::sysctl(CTL_KERN, KERN_PROCARGS, pid).string();
83 
84  if (args.isSome()) {
85  // At this point 'args' contains the parameters to 'exec'
86  // delimited by null bytes, i.e., "executable path", then
87  // "arg0" (the canonical executable name), then "arg1", then
88  // "arg2", etc. Sometimes there are no arguments (argc = 1) so
89  // all we care about is the "executable path", but when there
90  // are arguments we grab "arg0" and on assuming that "arg0"
91  // really is the canonical executable name.
92 
93  // Tokenize the args by the null byte ('\0').
94  std::vector<std::string> tokens =
95  strings::tokenize(args.get(), std::string(1, '\0'));
96 
97  if (!tokens.empty()) {
98  if (argc == 1) {
99  // When there are no arguments, all we care about is the
100  // "executable path".
101  command = tokens[0];
102  } else if (argc > 1) {
103  // When there are arguments, we skip the "executable path"
104  // and just grab "arg0" -> "argN", assuming "arg0" is the
105  // canonical executable name. In the case that we didn't
106  // get enough tokens back from KERN_PROCARGS the following
107  // code will end up just keeping 'command' None (i.e.,
108  // tokens.size() will be <= 0).
109  tokens.erase(tokens.begin()); // Remove path.
110 
111  // In practice it appeared that we might get an 'argc'
112  // which is greater than 'tokens.size()' which caused us
113  // to get an invalid iterator from 'tokens.begin() +
114  // argc', thus we now check that condition here.
115  if (static_cast<size_t>(argc) <= tokens.size()) {
116  tokens.erase(tokens.begin() + argc, tokens.end());
117  }
118 
119  // If any tokens are left, consider them the command!
120  if (tokens.size() > 0) {
121  command = strings::join(" ", tokens);
122  }
123  }
124  }
125  }
126  }
127  }
128 #endif
129 
130  // We also use proc_pidinfo() to get memory and CPU usage.
131  // NOTE: There are several pitfalls to using proc_pidinfo().
132  // In particular:
133  // -This will not work for many root processes.
134  // -This may not work for processes owned by other users.
135  // -However, this always works for processes owned by the same user.
136  // This beats using task_for_pid(), which only works for the same pid.
137  // For further discussion around these issues,
138  // see: http://code.google.com/p/psutil/issues/detail?id=297
139  proc_taskinfo task;
140  int size = proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &task, sizeof(task));
141 
142  // It appears that zombie processes on OS X do not have sessions and
143  // result in ESRCH.
144  int session = getsid(pid);
145 
146  if (size != sizeof(task)) {
147  return Process(process.kp_proc.p_pid,
148  process.kp_eproc.e_ppid,
149  process.kp_eproc.e_pgid,
150  session > 0 ? session : Option<pid_t>::none(),
151  None(),
152  None(),
153  None(),
154  command.getOrElse(std::string(process.kp_proc.p_comm)),
155  process.kp_proc.p_stat & SZOMB);
156  } else {
157  return Process(process.kp_proc.p_pid,
158  process.kp_eproc.e_ppid,
159  process.kp_eproc.e_pgid,
160  session > 0 ? session : Option<pid_t>::none(),
161  Bytes(task.pti_resident_size),
162  Nanoseconds(task.pti_total_user),
163  Nanoseconds(task.pti_total_system),
164  command.getOrElse(std::string(process.kp_proc.p_comm)),
165  process.kp_proc.p_stat & SZOMB);
166  }
167 }
168 
169 
170 inline Try<std::set<pid_t>> pids()
171 {
172  const Try<int> maxproc = os::sysctl(CTL_KERN, KERN_MAXPROC).integer();
173 
174  if (maxproc.isError()) {
175  return Error(maxproc.error());
176  }
177 
178  const Try<std::vector<kinfo_proc>> processes =
179  os::sysctl(CTL_KERN, KERN_PROC, KERN_PROC_ALL).table(maxproc.get());
180 
181  if (processes.isError()) {
182  return Error(processes.error());
183  }
184 
185  std::set<pid_t> result;
186  foreach (const kinfo_proc& process, processes.get()) {
187  result.insert(process.kp_proc.p_pid);
188  }
189  return result;
190 }
191 
192 
193 // Returns the total size of main and free memory.
194 inline Try<Memory> memory()
195 {
196  Memory memory;
197 
198  const Try<int64_t> totalMemory = os::sysctl(CTL_HW, HW_MEMSIZE).integer();
199 
200  if (totalMemory.isError()) {
201  return Error(totalMemory.error());
202  }
203  memory.total = Bytes(totalMemory.get());
204 
205  // Size of free memory is available in terms of number of
206  // free pages on Mac OS X.
207  const size_t pageSize = os::pagesize();
208 
209  unsigned int freeCount;
210  size_t length = sizeof(freeCount);
211 
212  if (sysctlbyname(
213  "vm.page_free_count",
214  &freeCount,
215  &length,
216  nullptr,
217  0) != 0) {
218  return ErrnoError();
219  }
220  memory.free = Bytes(freeCount * pageSize);
221 
222  struct xsw_usage usage;
223  length = sizeof(struct xsw_usage);
224  if (sysctlbyname(
225  "vm.swapusage",
226  &usage,
227  &length,
228  nullptr,
229  0) != 0) {
230  return ErrnoError();
231  }
232  memory.totalSwap = Bytes(usage.xsu_total * pageSize);
233  memory.freeSwap = Bytes(usage.xsu_avail * pageSize);
234 
235  return memory;
236 }
237 
238 } // namespace os {
239 
240 #endif // __STOUT_OS_OSX_HPP__
Definition: errorbase.hpp:36
T getOrElse(U &&u) const &
Definition: option.hpp:133
Try< Bytes > size(const std::string &path, const FollowSymlink follow=FollowSymlink::FOLLOW_SYMLINK)
Definition: stat.hpp:130
T & get()&
Definition: try.hpp:80
std::stringstream & join(std::stringstream &stream, const std::string &separator, T &&...args)
Definition: strings.hpp:307
Definition: check.hpp:33
Try< std::list< Process > > processes()
Definition: os.hpp:184
size_t pagesize()
Definition: pagesize.hpp:24
Definition: errorbase.hpp:50
Definition: posix_signalhandler.hpp:23
Definition: check.hpp:30
Try< std::string > string() const
Definition: sysctl.hpp:188
DWORD pid_t
Definition: windows.hpp:181
std::vector< std::string > tokenize(const std::string &s, const std::string &delims, const Option< size_t > &maxTokens=None())
Definition: strings.hpp:139
Try< ResourceStatistics > usage(pid_t pid, bool mem=true, bool cpus=true)
Table table(const Option< size_t > &length=None()) const
Definition: sysctl.hpp:229
bool isSome() const
Definition: try.hpp:77
Integer integer() const
Definition: sysctl.hpp:182
static Try error(const E &e)
Definition: try.hpp:43
Result< Process > process(pid_t pid)
Definition: freebsd.hpp:30
Definition: none.hpp:27
bool isError() const
Definition: try.hpp:78
Definition: executor.hpp:48
Definition: sysctl.hpp:59
Definition: duration.hpp:165
Definition: bytes.hpp:30
Try< Memory > memory()
Definition: freebsd.hpp:78
Try< std::set< pid_t > > pids()
Definition: freebsd.hpp:62