OLD | NEW |
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/process_info_snapshot.h" | 5 #include "chrome/browser/process_info_snapshot.h" |
6 | 6 |
| 7 #include <sys/sysctl.h> |
| 8 |
7 #include <sstream> | 9 #include <sstream> |
8 | 10 |
9 #include "base/command_line.h" | 11 #include "base/command_line.h" |
10 #include "base/logging.h" | 12 #include "base/logging.h" |
11 #include "base/string_number_conversions.h" | 13 #include "base/string_number_conversions.h" |
12 #include "base/string_util.h" | 14 #include "base/string_util.h" |
| 15 #include "base/sys_info.h" |
13 #include "base/threading/thread.h" | 16 #include "base/threading/thread.h" |
14 | 17 |
15 // Implementation for the Mac; calls '/bin/ps' for information when | |
16 // |Sample()| is called. | |
17 | |
18 // Default constructor. | 18 // Default constructor. |
19 ProcessInfoSnapshot::ProcessInfoSnapshot() { } | 19 ProcessInfoSnapshot::ProcessInfoSnapshot() { } |
20 | 20 |
21 // Destructor: just call |Reset()| to release everything. | 21 // Destructor: just call |Reset()| to release everything. |
22 ProcessInfoSnapshot::~ProcessInfoSnapshot() { | 22 ProcessInfoSnapshot::~ProcessInfoSnapshot() { |
23 Reset(); | 23 Reset(); |
24 } | 24 } |
25 | 25 |
26 const size_t ProcessInfoSnapshot::kMaxPidListSize = 1000; | 26 const size_t ProcessInfoSnapshot::kMaxPidListSize = 1000; |
27 | 27 |
| 28 static bool GetKInfoForProcessID(pid_t pid, kinfo_proc* kinfo) { |
| 29 int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid}; |
| 30 size_t len = sizeof(*kinfo); |
| 31 if (sysctl(mib, arraysize(mib), kinfo, &len, NULL, 0) != 0) { |
| 32 PLOG(ERROR) << "sysctl() for KERN_PROC"; |
| 33 return false; |
| 34 } |
| 35 |
| 36 if (len == 0) { |
| 37 // If the process isn't found then sysctl returns a length of 0. |
| 38 return false; |
| 39 } |
| 40 |
| 41 return true; |
| 42 } |
| 43 |
| 44 static bool GetExecutableNameForProcessID( |
| 45 pid_t pid, |
| 46 std::string* executable_name) { |
| 47 if (!executable_name) { |
| 48 NOTREACHED(); |
| 49 return false; |
| 50 } |
| 51 |
| 52 static int s_arg_max = 0; |
| 53 if (s_arg_max == 0) { |
| 54 int mib[] = {CTL_KERN, KERN_ARGMAX}; |
| 55 size_t size = sizeof(s_arg_max); |
| 56 if (sysctl(mib, arraysize(mib), &s_arg_max, &size, NULL, 0) != 0) |
| 57 PLOG(ERROR) << "sysctl() for KERN_ARGMAX"; |
| 58 } |
| 59 |
| 60 if (s_arg_max == 0) |
| 61 return false; |
| 62 |
| 63 int mib[] = {CTL_KERN, KERN_PROCARGS, pid}; |
| 64 size_t size = s_arg_max; |
| 65 executable_name->resize(s_arg_max + 1); |
| 66 if (sysctl(mib, arraysize(mib), &(*executable_name)[0], |
| 67 &size, NULL, 0) != 0) { |
| 68 // Don't log the error since it's normal for this to fail. |
| 69 return false; |
| 70 } |
| 71 |
| 72 // KERN_PROCARGS returns multiple NULL terminated strings. Truncate |
| 73 // executable_name to just the first string. |
| 74 size_t end_pos = executable_name->find('\0'); |
| 75 if (end_pos == std::string::npos) { |
| 76 return false; |
| 77 } |
| 78 |
| 79 executable_name->resize(end_pos); |
| 80 return true; |
| 81 } |
| 82 |
| 83 // Converts a byte unit such as 'K' or 'M' into the scale for the unit. |
| 84 // The scale can then be used to calculate the number of bytes in a value. |
| 85 // The units are based on humanize_number(). See: |
| 86 // http://www.opensource.apple.com/source/libutil/libutil-21/humanize_number.c |
| 87 static bool ConvertByteUnitToScale(char unit, uint64_t* out_scale) { |
| 88 int shift = 0; |
| 89 switch (unit) { |
| 90 case 'B': |
| 91 shift = 0; |
| 92 break; |
| 93 case 'K': |
| 94 case 'k': |
| 95 shift = 1; |
| 96 break; |
| 97 case 'M': |
| 98 shift = 2; |
| 99 break; |
| 100 case 'G': |
| 101 shift = 3; |
| 102 break; |
| 103 case 'T': |
| 104 shift = 4; |
| 105 break; |
| 106 case 'P': |
| 107 shift = 5; |
| 108 break; |
| 109 case 'E': |
| 110 shift = 6; |
| 111 break; |
| 112 default: |
| 113 return false; |
| 114 } |
| 115 |
| 116 uint64_t scale = 1; |
| 117 for (int i = 0; i < shift; i++) |
| 118 scale *= 1024; |
| 119 *out_scale = scale; |
| 120 |
| 121 return true; |
| 122 } |
| 123 |
28 // Capture the information by calling '/bin/ps'. | 124 // Capture the information by calling '/bin/ps'. |
29 // Note: we ignore the "tsiz" (text size) display option of ps because it's | 125 // Note: we ignore the "tsiz" (text size) display option of ps because it's |
30 // always zero (tested on 10.5 and 10.6). | 126 // always zero (tested on 10.5 and 10.6). |
31 bool ProcessInfoSnapshot::Sample(std::vector<base::ProcessId> pid_list) { | 127 static bool GetProcessMemoryInfoUsingPS( |
32 const char* kPsPathName = "/bin/ps"; | 128 const std::vector<base::ProcessId>& pid_list, |
33 Reset(); | 129 std::map<int,ProcessInfoSnapshot::ProcInfoEntry>& proc_info_entries) { |
34 | 130 const char kPsPathName[] = "/bin/ps"; |
35 // Nothing to do if no PIDs given. | |
36 if (pid_list.size() == 0) | |
37 return true; | |
38 if (pid_list.size() > kMaxPidListSize) { | |
39 // The spec says |pid_list| *must* not have more than this many entries. | |
40 NOTREACHED(); | |
41 return false; | |
42 } | |
43 | |
44 std::vector<std::string> argv; | 131 std::vector<std::string> argv; |
45 argv.push_back(kPsPathName); | 132 argv.push_back(kPsPathName); |
46 // Get PID, PPID, (real) UID, effective UID, resident set size, virtual memory | 133 |
47 // size, and command. | 134 // Get resident set size, virtual memory size. |
48 argv.push_back("-o"); | 135 argv.push_back("-o"); |
49 argv.push_back("pid=,ppid=,ruid=,uid=,rss=,vsz=,comm="); | 136 argv.push_back("pid=,rss=,vsz="); |
50 // Only display the specified PIDs. | 137 // Only display the specified PIDs. |
51 for (std::vector<base::ProcessId>::iterator it = pid_list.begin(); | 138 for (std::vector<base::ProcessId>::const_iterator it = pid_list.begin(); |
52 it != pid_list.end(); ++it) { | 139 it != pid_list.end(); ++it) { |
53 argv.push_back("-p"); | 140 argv.push_back("-p"); |
54 argv.push_back(base::Int64ToString(static_cast<int64>(*it))); | 141 argv.push_back(base::Int64ToString(static_cast<int64>(*it))); |
55 } | 142 } |
56 | 143 |
57 std::string output; | 144 std::string output; |
58 CommandLine command_line(argv); | 145 CommandLine command_line(argv); |
59 // Limit output read to a megabyte for safety. | 146 // Limit output read to a megabyte for safety. |
60 if (!base::GetAppOutputRestricted(command_line, &output, 1024 * 1024)) { | 147 if (!base::GetAppOutputRestricted(command_line, &output, 1024 * 1024)) { |
61 LOG(ERROR) << "Failure running " << kPsPathName << " to acquire data."; | 148 LOG(ERROR) << "Failure running " << kPsPathName << " to acquire data."; |
62 return false; | 149 return false; |
63 } | 150 } |
64 | 151 |
65 std::istringstream in(output, std::istringstream::in); | 152 std::istringstream in(output, std::istringstream::in); |
66 std::string line; | 153 std::string line; |
67 | 154 |
68 // Process lines until done. | 155 // Process lines until done. |
69 while (true) { | 156 while (true) { |
70 ProcInfoEntry proc_info; | |
71 | |
72 // The format is as specified above to ps (see ps(1)): | 157 // The format is as specified above to ps (see ps(1)): |
73 // "-o pid=,ppid=,ruid=,uid=,rss=,vsz=,comm=". | 158 // "-o pid=,rss=,vsz=". |
74 // Try to read the PID; if we get it, we should be able to get the rest of | 159 // Try to read the PID; if we get it, we should be able to get the rest of |
75 // the line. | 160 // the line. |
76 in >> proc_info.pid; | 161 pid_t pid; |
| 162 in >> pid; |
77 if (in.eof()) | 163 if (in.eof()) |
78 break; | 164 break; |
79 in >> proc_info.ppid; | 165 |
80 in >> proc_info.uid; | 166 ProcessInfoSnapshot::ProcInfoEntry proc_info = proc_info_entries[pid]; |
81 in >> proc_info.euid; | 167 proc_info.pid = pid; |
82 in >> proc_info.rss; | 168 in >> proc_info.rss; |
83 in >> proc_info.vsize; | 169 in >> proc_info.vsize; |
| 170 proc_info.rss *= 1024; // Convert from kilobytes to bytes. |
| 171 proc_info.vsize *= 1024; |
84 in.ignore(1, ' '); // Eat the space. | 172 in.ignore(1, ' '); // Eat the space. |
85 std::getline(in, proc_info.command); // Get the rest of the line. | 173 std::getline(in, proc_info.command); // Get the rest of the line. |
86 if (!in.good()) { | 174 if (!in.good()) { |
87 LOG(ERROR) << "Error parsing output from " << kPsPathName << "."; | 175 LOG(ERROR) << "Error parsing output from " << kPsPathName << "."; |
88 return false; | 176 return false; |
89 } | 177 } |
90 | 178 |
91 // Make sure the new PID isn't already in our list. | |
92 if (proc_info_entries_.find(proc_info.pid) != proc_info_entries_.end()) { | |
93 LOG(ERROR) << "Duplicate PID in output from " << kPsPathName << "."; | |
94 return false; | |
95 } | |
96 | |
97 if (!proc_info.pid || ! proc_info.vsize) { | 179 if (!proc_info.pid || ! proc_info.vsize) { |
98 LOG(WARNING) << "Invalid data from " << kPsPathName << "."; | 180 LOG(WARNING) << "Invalid data from " << kPsPathName << "."; |
99 return false; | 181 return false; |
100 } | 182 } |
101 | 183 |
102 // Record the process information. | 184 // Record the process information. |
103 proc_info_entries_[proc_info.pid] = proc_info; | 185 proc_info_entries[proc_info.pid] = proc_info; |
104 } | 186 } |
105 | 187 |
106 return true; | 188 return true; |
107 } | 189 } |
108 | 190 |
| 191 static bool GetProcessMemoryInfoUsingTop( |
| 192 std::map<int,ProcessInfoSnapshot::ProcInfoEntry>& proc_info_entries) { |
| 193 const char kTopPathName[] = "/usr/bin/top"; |
| 194 std::vector<std::string> argv; |
| 195 argv.push_back(kTopPathName); |
| 196 |
| 197 // -stats tells top to print just the given fields as ordered. |
| 198 argv.push_back("-stats"); |
| 199 argv.push_back("pid," // Process ID |
| 200 "rsize," // Resident memory |
| 201 "rshrd," // Resident shared memory |
| 202 "rprvt," // Resident private memory |
| 203 "vsize"); // Total virtual memory |
| 204 // Run top in logging (non-interactive) mode. |
| 205 argv.push_back("-l"); |
| 206 argv.push_back("1"); |
| 207 // Set the delay between updates to 0. |
| 208 argv.push_back("-s"); |
| 209 argv.push_back("0"); |
| 210 |
| 211 std::string output; |
| 212 CommandLine command_line(argv); |
| 213 // Limit output read to a megabyte for safety. |
| 214 if (!base::GetAppOutputRestricted(command_line, &output, 1024 * 1024)) { |
| 215 LOG(ERROR) << "Failure running " << kTopPathName << " to acquire data."; |
| 216 return false; |
| 217 } |
| 218 |
| 219 // Process lines until done. Lines should look something like this: |
| 220 // PID RSIZE RSHRD RPRVT VSIZE |
| 221 // 58539 1276K+ 336K+ 740K+ 2378M+ |
| 222 // 58485 1888K+ 592K+ 1332K+ 2383M+ |
| 223 std::istringstream top_in(output, std::istringstream::in); |
| 224 std::string line; |
| 225 while (std::getline(top_in, line)) { |
| 226 std::istringstream in(line, std::istringstream::in); |
| 227 |
| 228 // Try to read the PID. |
| 229 pid_t pid; |
| 230 in >> pid; |
| 231 if (in.fail()) |
| 232 continue; |
| 233 |
| 234 // Make sure that caller is interested in this process. |
| 235 if (proc_info_entries.find(pid) == proc_info_entries.end()) |
| 236 continue; |
| 237 |
| 238 // Skip the - or + sign that top puts after the pid. |
| 239 in.get(); |
| 240 |
| 241 uint64_t values[4]; |
| 242 size_t i; |
| 243 for (i = 0; i < arraysize(values); i++) { |
| 244 in >> values[i]; |
| 245 if (in.fail()) |
| 246 break; |
| 247 std::string unit; |
| 248 in >> unit; |
| 249 if (in.fail()) |
| 250 break; |
| 251 |
| 252 if (unit.size() == 0) |
| 253 break; |
| 254 |
| 255 uint64_t scale; |
| 256 if (!ConvertByteUnitToScale(unit[0], &scale)) |
| 257 break; |
| 258 values[i] *= scale; |
| 259 } |
| 260 if (i != arraysize(values)) |
| 261 continue; |
| 262 |
| 263 ProcessInfoSnapshot::ProcInfoEntry proc_info = proc_info_entries[pid]; |
| 264 proc_info.rss = values[0]; |
| 265 proc_info.rshrd = values[1]; |
| 266 proc_info.rprvt = values[2]; |
| 267 proc_info.vsize = values[3]; |
| 268 // Record the process information. |
| 269 proc_info_entries[proc_info.pid] = proc_info; |
| 270 } |
| 271 |
| 272 return true; |
| 273 } |
| 274 |
| 275 static bool GetProcessMemoryInfoUsingTop_10_5( |
| 276 std::map<int,ProcessInfoSnapshot::ProcInfoEntry>& proc_info_entries) { |
| 277 const char kTopPathName[] = "/usr/bin/top"; |
| 278 std::vector<std::string> argv; |
| 279 argv.push_back(kTopPathName); |
| 280 |
| 281 // -p tells top to print just the given fields as ordered. |
| 282 argv.push_back("-p"); |
| 283 argv.push_back("^aaaaaaaaaaaaaaaaaaaa " // Process ID (PID) |
| 284 "^jjjjjjjjjjjjjjjjjjjj " // Resident memory (RSIZE) |
| 285 "^iiiiiiiiiiiiiiiiiiii " // Resident shared memory (RSHRD) |
| 286 "^hhhhhhhhhhhhhhhhhhhh " // Resident private memory (RPRVT) |
| 287 "^llllllllllllllllllll"); // Total virtual memory (VSIZE) |
| 288 // Run top in logging (non-interactive) mode. |
| 289 argv.push_back("-l"); |
| 290 argv.push_back("1"); |
| 291 // Set the delay between updates to 0. |
| 292 argv.push_back("-s"); |
| 293 argv.push_back("0"); |
| 294 |
| 295 std::string output; |
| 296 CommandLine command_line(argv); |
| 297 // Limit output read to a megabyte for safety. |
| 298 if (!base::GetAppOutputRestricted(command_line, &output, 1024 * 1024)) { |
| 299 LOG(ERROR) << "Failure running " << kTopPathName << " to acquire data."; |
| 300 return false; |
| 301 } |
| 302 |
| 303 // Process lines until done. Lines should look something like this: |
| 304 // PID RSIZE RSHRD RPRVT VSIZE |
| 305 // 16943 815104 262144 290816 18489344 |
| 306 // 16922 954368 720896 278528 18976768 |
| 307 std::istringstream top_in(output, std::istringstream::in); |
| 308 std::string line; |
| 309 while (std::getline(top_in, line)) { |
| 310 std::istringstream in(line, std::istringstream::in); |
| 311 |
| 312 // Try to read the PID. |
| 313 pid_t pid; |
| 314 in >> pid; |
| 315 if (in.fail()) |
| 316 continue; |
| 317 |
| 318 // Make sure that caller is interested in this process. |
| 319 if (proc_info_entries.find(pid) == proc_info_entries.end()) |
| 320 continue; |
| 321 |
| 322 uint64_t values[4]; |
| 323 size_t i; |
| 324 for (i = 0; i < arraysize(values); i++) { |
| 325 in >> values[i]; |
| 326 if (in.fail()) |
| 327 break; |
| 328 } |
| 329 if (i != arraysize(values)) |
| 330 continue; |
| 331 |
| 332 ProcessInfoSnapshot::ProcInfoEntry proc_info = proc_info_entries[pid]; |
| 333 proc_info.rss = values[0]; |
| 334 proc_info.rshrd = values[1]; |
| 335 proc_info.rprvt = values[2]; |
| 336 proc_info.vsize = values[3]; |
| 337 // Record the process information. |
| 338 proc_info_entries[proc_info.pid] = proc_info; |
| 339 } |
| 340 |
| 341 return true; |
| 342 } |
| 343 |
| 344 |
| 345 bool ProcessInfoSnapshot::Sample(std::vector<base::ProcessId> pid_list) { |
| 346 Reset(); |
| 347 |
| 348 // Nothing to do if no PIDs given. |
| 349 if (pid_list.size() == 0) |
| 350 return true; |
| 351 if (pid_list.size() > kMaxPidListSize) { |
| 352 // The spec says |pid_list| *must* not have more than this many entries. |
| 353 NOTREACHED(); |
| 354 return false; |
| 355 } |
| 356 |
| 357 // Get basic process info from KERN_PROC. |
| 358 for (std::vector<base::ProcessId>::iterator it = pid_list.begin(); |
| 359 it != pid_list.end(); ++it) { |
| 360 ProcInfoEntry proc_info; |
| 361 proc_info.pid = *it; |
| 362 |
| 363 kinfo_proc kinfo; |
| 364 if (!GetKInfoForProcessID(*it, &kinfo)) |
| 365 return false; |
| 366 |
| 367 proc_info.ppid = kinfo.kp_eproc.e_ppid; |
| 368 proc_info.uid = kinfo.kp_eproc.e_pcred.p_ruid; |
| 369 proc_info.euid = kinfo.kp_eproc.e_ucred.cr_uid; |
| 370 // Note, p_comm is truncated to 16 characters. |
| 371 proc_info.command = kinfo.kp_proc.p_comm; |
| 372 proc_info_entries_[*it] = proc_info; |
| 373 } |
| 374 |
| 375 // Use KERN_PROCARGS to get the full executable name. This may fail if this |
| 376 // process doesn't have privileges to inspect the target process. |
| 377 for (std::vector<base::ProcessId>::iterator it = pid_list.begin(); |
| 378 it != pid_list.end(); ++it) { |
| 379 std::string exectuable_name; |
| 380 if (GetExecutableNameForProcessID(*it, &exectuable_name)) { |
| 381 ProcInfoEntry proc_info = proc_info_entries_[*it]; |
| 382 proc_info.command = exectuable_name; |
| 383 } |
| 384 } |
| 385 |
| 386 // Get memory information using top. |
| 387 bool memory_info_success = false; |
| 388 int32 major, minor, bugfix; |
| 389 base::SysInfo::OperatingSystemVersionNumbers(&major, &minor, &bugfix); |
| 390 if (major == 10 && minor == 5) |
| 391 memory_info_success = GetProcessMemoryInfoUsingTop_10_5(proc_info_entries_); |
| 392 else if ((major == 10 && minor >= 6) || major > 10) |
| 393 memory_info_success = GetProcessMemoryInfoUsingTop(proc_info_entries_); |
| 394 |
| 395 // If top didn't work then fall back to ps. |
| 396 if (!memory_info_success) { |
| 397 memory_info_success = GetProcessMemoryInfoUsingPS(pid_list, |
| 398 proc_info_entries_); |
| 399 } |
| 400 |
| 401 return memory_info_success; |
| 402 } |
| 403 |
109 // Clear all the stored information. | 404 // Clear all the stored information. |
110 void ProcessInfoSnapshot::Reset() { | 405 void ProcessInfoSnapshot::Reset() { |
111 proc_info_entries_.clear(); | 406 proc_info_entries_.clear(); |
112 } | 407 } |
113 | 408 |
114 bool ProcessInfoSnapshot::GetProcInfo(int pid, | 409 bool ProcessInfoSnapshot::GetProcInfo(int pid, |
115 ProcInfoEntry* proc_info) const { | 410 ProcInfoEntry* proc_info) const { |
116 std::map<int,ProcInfoEntry>::const_iterator it = proc_info_entries_.find(pid); | 411 std::map<int,ProcInfoEntry>::const_iterator it = proc_info_entries_.find(pid); |
117 if (it == proc_info_entries_.end()) | 412 if (it == proc_info_entries_.end()) |
118 return false; | 413 return false; |
(...skipping 13 matching lines...) Expand all Loading... |
132 | 427 |
133 // Failure of |GetProcInfo()| is "normal", due to racing. | 428 // Failure of |GetProcInfo()| is "normal", due to racing. |
134 ProcInfoEntry proc_info; | 429 ProcInfoEntry proc_info; |
135 if (!GetProcInfo(pid, &proc_info)) { | 430 if (!GetProcInfo(pid, &proc_info)) { |
136 usage->priv = 0; | 431 usage->priv = 0; |
137 usage->mapped = 0; | 432 usage->mapped = 0; |
138 usage->image = 0; | 433 usage->image = 0; |
139 return false; | 434 return false; |
140 } | 435 } |
141 | 436 |
142 usage->priv = proc_info.vsize; | 437 usage->priv = proc_info.vsize / 1024; |
143 usage->mapped = 0; | 438 usage->mapped = 0; |
144 usage->image = 0; | 439 usage->image = 0; |
145 return true; | 440 return true; |
146 } | 441 } |
147 | 442 |
148 bool ProcessInfoSnapshot::GetWorkingSetKBytesOfPID( | 443 bool ProcessInfoSnapshot::GetWorkingSetKBytesOfPID( |
149 int pid, | 444 int pid, |
150 base::WorkingSetKBytes* ws_usage) const { | 445 base::WorkingSetKBytes* ws_usage) const { |
151 // Try to avoid crashing on a bug; stats aren't usually so crucial. | 446 // Try to avoid crashing on a bug; stats aren't usually so crucial. |
152 if (!ws_usage) { | 447 if (!ws_usage) { |
153 NOTREACHED(); | 448 NOTREACHED(); |
154 return false; | 449 return false; |
155 } | 450 } |
156 | 451 |
157 // Failure of |GetProcInfo()| is "normal", due to racing. | 452 // Failure of |GetProcInfo()| is "normal", due to racing. |
158 ProcInfoEntry proc_info; | 453 ProcInfoEntry proc_info; |
159 if (!GetProcInfo(pid, &proc_info)) { | 454 if (!GetProcInfo(pid, &proc_info)) { |
160 ws_usage->priv = 0; | 455 ws_usage->priv = 0; |
161 ws_usage->shareable = 0; | 456 ws_usage->shareable = 0; |
162 ws_usage->shared = 0; | 457 ws_usage->shared = 0; |
163 return false; | 458 return false; |
164 } | 459 } |
165 | 460 |
166 ws_usage->priv = 0; | 461 ws_usage->priv = proc_info.rprvt / 1024; |
167 ws_usage->shareable = proc_info.rss; | 462 ws_usage->shareable = proc_info.rss / 1024; |
168 ws_usage->shared = 0; | 463 ws_usage->shared = proc_info.rshrd / 1024; |
169 return true; | 464 return true; |
170 } | 465 } |
OLD | NEW |