Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 /* | 1 /* |
| 2 * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. | 2 * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. |
| 3 * | 3 * |
| 4 * Use of this source code is governed by a BSD-style license | 4 * Use of this source code is governed by a BSD-style license |
| 5 * that can be found in the LICENSE file in the root of the source | 5 * that can be found in the LICENSE file in the root of the source |
| 6 * tree. An additional intellectual property rights grant can be found | 6 * tree. An additional intellectual property rights grant can be found |
| 7 * in the file PATENTS. All contributing project authors may | 7 * in the file PATENTS. All contributing project authors may |
| 8 * be found in the AUTHORS file in the root of the source tree. | 8 * be found in the AUTHORS file in the root of the source tree. |
| 9 */ | 9 */ |
| 10 | 10 |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 24 #include "webrtc/system_wrappers/include/clock.h" | 24 #include "webrtc/system_wrappers/include/clock.h" |
| 25 #include "webrtc/video_frame.h" | 25 #include "webrtc/video_frame.h" |
| 26 | 26 |
| 27 #if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) | 27 #if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) |
| 28 #include <mach/mach.h> | 28 #include <mach/mach.h> |
| 29 #endif // defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) | 29 #endif // defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) |
| 30 | 30 |
| 31 namespace webrtc { | 31 namespace webrtc { |
| 32 | 32 |
| 33 namespace { | 33 namespace { |
| 34 const int64_t kProcessIntervalMs = 5000; | 34 const int64_t ktCheckForOveruseIntervalMs = 5000; |
|
åsapersson
2016/08/24 08:59:04
kCheck..?
perkj_webrtc
2016/09/01 10:03:29
Done.
| |
| 35 const int64_t kTimeToFirstCheckForOveruseMs = 100; | |
| 35 | 36 |
| 36 // Delay between consecutive rampups. (Used for quick recovery.) | 37 // Delay between consecutive rampups. (Used for quick recovery.) |
| 37 const int kQuickRampUpDelayMs = 10 * 1000; | 38 const int kQuickRampUpDelayMs = 10 * 1000; |
| 38 // Delay between rampup attempts. Initially uses standard, scales up to max. | 39 // Delay between rampup attempts. Initially uses standard, scales up to max. |
| 39 const int kStandardRampUpDelayMs = 40 * 1000; | 40 const int kStandardRampUpDelayMs = 40 * 1000; |
| 40 const int kMaxRampUpDelayMs = 240 * 1000; | 41 const int kMaxRampUpDelayMs = 240 * 1000; |
| 41 // Expontential back-off factor, to prevent annoying up-down behaviour. | 42 // Expontential back-off factor, to prevent annoying up-down behaviour. |
| 42 const double kRampUpBackoffFactor = 2.0; | 43 const double kRampUpBackoffFactor = 2.0; |
| 43 | 44 |
| 44 // Max number of overuses detected before always applying the rampup delay. | 45 // Max number of overuses detected before always applying the rampup delay. |
| (...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 163 const float kWeightFactorFrameDiff; | 164 const float kWeightFactorFrameDiff; |
| 164 const float kWeightFactorProcessing; | 165 const float kWeightFactorProcessing; |
| 165 const float kInitialSampleDiffMs; | 166 const float kInitialSampleDiffMs; |
| 166 const float kMaxSampleDiffMs; | 167 const float kMaxSampleDiffMs; |
| 167 uint64_t count_; | 168 uint64_t count_; |
| 168 const CpuOveruseOptions options_; | 169 const CpuOveruseOptions options_; |
| 169 std::unique_ptr<rtc::ExpFilter> filtered_processing_ms_; | 170 std::unique_ptr<rtc::ExpFilter> filtered_processing_ms_; |
| 170 std::unique_ptr<rtc::ExpFilter> filtered_frame_diff_ms_; | 171 std::unique_ptr<rtc::ExpFilter> filtered_frame_diff_ms_; |
| 171 }; | 172 }; |
| 172 | 173 |
| 174 class OveruseFrameDetector::CheckOveruseTask : public rtc::QueuedTask { | |
| 175 public: | |
| 176 explicit CheckOveruseTask(OveruseFrameDetector* overuse_detector) | |
| 177 : overuse_detector_(overuse_detector) { | |
| 178 rtc::TaskQueue::Current()->PostDelayedTask( | |
| 179 std::unique_ptr<rtc::QueuedTask>(this), kTimeToFirstCheckForOveruseMs); | |
| 180 } | |
| 181 | |
| 182 void Stop() { | |
| 183 RTC_CHECK(task_checker_.CalledSequentially()); | |
| 184 overuse_detector_ = nullptr; | |
| 185 } | |
| 186 | |
| 187 private: | |
| 188 bool Run() override { | |
| 189 RTC_CHECK(task_checker_.CalledSequentially()); | |
| 190 if (!overuse_detector_) | |
| 191 return true; // This will make the task queue delete this task. | |
| 192 overuse_detector_->CheckForOveruse(); | |
| 193 | |
| 194 rtc::TaskQueue::Current()->PostDelayedTask( | |
| 195 std::unique_ptr<rtc::QueuedTask>(this), ktCheckForOveruseIntervalMs); | |
| 196 // Return false to prevent this task from being deleted. Ownership has been | |
| 197 // transferred to the task queue when PostDelayedTask was called. | |
| 198 return false; | |
| 199 } | |
| 200 rtc::SequencedTaskChecker task_checker_; | |
| 201 OveruseFrameDetector* overuse_detector_; | |
| 202 }; | |
| 203 | |
| 173 OveruseFrameDetector::OveruseFrameDetector( | 204 OveruseFrameDetector::OveruseFrameDetector( |
| 174 Clock* clock, | 205 Clock* clock, |
| 175 const CpuOveruseOptions& options, | 206 const CpuOveruseOptions& options, |
| 176 CpuOveruseObserver* observer, | 207 CpuOveruseObserver* observer, |
| 177 EncodedFrameObserver* encoder_timing, | 208 EncodedFrameObserver* encoder_timing, |
| 178 CpuOveruseMetricsObserver* metrics_observer) | 209 CpuOveruseMetricsObserver* metrics_observer) |
| 179 : options_(options), | 210 : check_over_use_task_(nullptr), |
| 211 options_(options), | |
| 180 observer_(observer), | 212 observer_(observer), |
| 181 encoder_timing_(encoder_timing), | 213 encoder_timing_(encoder_timing), |
| 182 metrics_observer_(metrics_observer), | 214 metrics_observer_(metrics_observer), |
| 183 clock_(clock), | 215 clock_(clock), |
| 184 num_process_times_(0), | 216 num_process_times_(0), |
| 185 last_capture_time_ms_(-1), | 217 last_capture_time_ms_(-1), |
| 186 last_processed_capture_time_ms_(-1), | 218 last_processed_capture_time_ms_(-1), |
| 187 num_pixels_(0), | 219 num_pixels_(0), |
| 188 next_process_time_ms_(clock_->TimeInMilliseconds()), | |
| 189 last_overuse_time_ms_(-1), | 220 last_overuse_time_ms_(-1), |
| 190 checks_above_threshold_(0), | 221 checks_above_threshold_(0), |
| 191 num_overuse_detections_(0), | 222 num_overuse_detections_(0), |
| 192 last_rampup_time_ms_(-1), | 223 last_rampup_time_ms_(-1), |
| 193 in_quick_rampup_(false), | 224 in_quick_rampup_(false), |
| 194 current_rampup_delay_ms_(kStandardRampUpDelayMs), | 225 current_rampup_delay_ms_(kStandardRampUpDelayMs), |
| 195 usage_(new SendProcessingUsage(options)) { | 226 usage_(new SendProcessingUsage(options)) { |
| 196 processing_thread_.DetachFromThread(); | 227 task_checker_.Detach(); |
| 197 } | 228 } |
| 198 | 229 |
| 199 OveruseFrameDetector::~OveruseFrameDetector() { | 230 OveruseFrameDetector::~OveruseFrameDetector() { |
| 231 RTC_DCHECK(!check_over_use_task_) << "StopCheckForOverUse must be called."; | |
|
åsapersson
2016/08/24 08:59:04
nit: OverUse->Overuse
perkj_webrtc
2016/09/01 10:03:29
Done.
| |
| 232 } | |
| 233 | |
| 234 void OveruseFrameDetector::StartCheckForOveruse() { | |
| 235 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_); | |
| 236 RTC_DCHECK(!check_over_use_task_); | |
| 237 check_over_use_task_ = new CheckOveruseTask(this); | |
| 238 } | |
| 239 void OveruseFrameDetector::StopCheckForOveruse() { | |
| 240 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_); | |
| 241 check_over_use_task_->Stop(); | |
| 242 check_over_use_task_ = nullptr; | |
| 200 } | 243 } |
| 201 | 244 |
| 202 void OveruseFrameDetector::EncodedFrameTimeMeasured(int encode_duration_ms) { | 245 void OveruseFrameDetector::EncodedFrameTimeMeasured(int encode_duration_ms) { |
| 246 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_); | |
| 203 if (!metrics_) | 247 if (!metrics_) |
| 204 metrics_ = rtc::Optional<CpuOveruseMetrics>(CpuOveruseMetrics()); | 248 metrics_ = rtc::Optional<CpuOveruseMetrics>(CpuOveruseMetrics()); |
| 205 metrics_->encode_usage_percent = usage_->Value(); | 249 metrics_->encode_usage_percent = usage_->Value(); |
| 206 | 250 |
| 207 metrics_observer_->OnEncodedFrameTimeMeasured(encode_duration_ms, *metrics_); | 251 metrics_observer_->OnEncodedFrameTimeMeasured(encode_duration_ms, *metrics_); |
| 208 } | 252 } |
| 209 | 253 |
| 210 int64_t OveruseFrameDetector::TimeUntilNextProcess() { | |
| 211 RTC_DCHECK(processing_thread_.CalledOnValidThread()); | |
| 212 return next_process_time_ms_ - clock_->TimeInMilliseconds(); | |
| 213 } | |
| 214 | |
| 215 bool OveruseFrameDetector::FrameSizeChanged(int num_pixels) const { | 254 bool OveruseFrameDetector::FrameSizeChanged(int num_pixels) const { |
| 255 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_); | |
|
åsapersson
2016/08/24 08:59:04
Is the check needed for private methods?
perkj_webrtc
2016/09/01 10:03:29
Yes- , if you use GUARDED_BY(..) this is needed on
| |
| 216 if (num_pixels != num_pixels_) { | 256 if (num_pixels != num_pixels_) { |
| 217 return true; | 257 return true; |
| 218 } | 258 } |
| 219 return false; | 259 return false; |
| 220 } | 260 } |
| 221 | 261 |
| 222 bool OveruseFrameDetector::FrameTimeoutDetected(int64_t now) const { | 262 bool OveruseFrameDetector::FrameTimeoutDetected(int64_t now) const { |
| 263 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_); | |
| 223 if (last_capture_time_ms_ == -1) | 264 if (last_capture_time_ms_ == -1) |
| 224 return false; | 265 return false; |
| 225 return (now - last_capture_time_ms_) > options_.frame_timeout_interval_ms; | 266 return (now - last_capture_time_ms_) > options_.frame_timeout_interval_ms; |
| 226 } | 267 } |
| 227 | 268 |
| 228 void OveruseFrameDetector::ResetAll(int num_pixels) { | 269 void OveruseFrameDetector::ResetAll(int num_pixels) { |
| 270 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_); | |
| 229 num_pixels_ = num_pixels; | 271 num_pixels_ = num_pixels; |
| 230 usage_->Reset(); | 272 usage_->Reset(); |
| 231 frame_timing_.clear(); | 273 frame_timing_.clear(); |
| 232 last_capture_time_ms_ = -1; | 274 last_capture_time_ms_ = -1; |
| 233 last_processed_capture_time_ms_ = -1; | 275 last_processed_capture_time_ms_ = -1; |
| 234 num_process_times_ = 0; | 276 num_process_times_ = 0; |
| 235 metrics_ = rtc::Optional<CpuOveruseMetrics>(); | 277 metrics_ = rtc::Optional<CpuOveruseMetrics>(); |
| 236 } | 278 } |
| 237 | 279 |
| 238 void OveruseFrameDetector::FrameCaptured(const VideoFrame& frame) { | 280 void OveruseFrameDetector::FrameCaptured(const VideoFrame& frame, |
| 239 rtc::CritScope cs(&crit_); | 281 int64_t time_when_first_seen) { |
|
åsapersson
2016/08/24 08:59:04
time_when_first_seen_ms
perkj_webrtc
2016/09/01 10:03:29
Done.
| |
| 282 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_); | |
| 240 | 283 |
| 241 int64_t now = clock_->TimeInMilliseconds(); | |
| 242 if (FrameSizeChanged(frame.width() * frame.height()) || | 284 if (FrameSizeChanged(frame.width() * frame.height()) || |
| 243 FrameTimeoutDetected(now)) { | 285 FrameTimeoutDetected(time_when_first_seen)) { |
| 244 ResetAll(frame.width() * frame.height()); | 286 ResetAll(frame.width() * frame.height()); |
| 245 } | 287 } |
| 246 | 288 |
| 247 if (last_capture_time_ms_ != -1) | 289 if (last_capture_time_ms_ != -1) |
| 248 usage_->AddCaptureSample(now - last_capture_time_ms_); | 290 usage_->AddCaptureSample(time_when_first_seen - last_capture_time_ms_); |
| 249 | 291 |
| 250 last_capture_time_ms_ = now; | 292 last_capture_time_ms_ = time_when_first_seen; |
| 251 | 293 |
| 252 frame_timing_.push_back( | 294 frame_timing_.push_back(FrameTiming(frame.ntp_time_ms(), frame.timestamp(), |
| 253 FrameTiming(frame.ntp_time_ms(), frame.timestamp(), now)); | 295 time_when_first_seen)); |
| 254 } | 296 } |
| 255 | 297 |
| 256 void OveruseFrameDetector::FrameSent(uint32_t timestamp) { | 298 void OveruseFrameDetector::FrameSent(uint32_t timestamp, |
| 257 rtc::CritScope cs(&crit_); | 299 int64_t time_sent_in_ms) { |
| 300 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_); | |
| 258 // Delay before reporting actual encoding time, used to have the ability to | 301 // Delay before reporting actual encoding time, used to have the ability to |
| 259 // detect total encoding time when encoding more than one layer. Encoding is | 302 // detect total encoding time when encoding more than one layer. Encoding is |
| 260 // here assumed to finish within a second (or that we get enough long-time | 303 // here assumed to finish within a second (or that we get enough long-time |
| 261 // samples before one second to trigger an overuse even when this is not the | 304 // samples before one second to trigger an overuse even when this is not the |
| 262 // case). | 305 // case). |
| 263 static const int64_t kEncodingTimeMeasureWindowMs = 1000; | 306 static const int64_t kEncodingTimeMeasureWindowMs = 1000; |
| 264 int64_t now = clock_->TimeInMilliseconds(); | |
| 265 for (auto& it : frame_timing_) { | 307 for (auto& it : frame_timing_) { |
| 266 if (it.timestamp == timestamp) { | 308 if (it.timestamp == timestamp) { |
| 267 it.last_send_ms = now; | 309 it.last_send_ms = time_sent_in_ms; |
| 268 break; | 310 break; |
| 269 } | 311 } |
| 270 } | 312 } |
| 271 // TODO(pbos): Handle the case/log errors when not finding the corresponding | 313 // TODO(pbos): Handle the case/log errors when not finding the corresponding |
| 272 // frame (either very slow encoding or incorrect wrong timestamps returned | 314 // frame (either very slow encoding or incorrect wrong timestamps returned |
| 273 // from the encoder). | 315 // from the encoder). |
| 274 // This is currently the case for all frames on ChromeOS, so logging them | 316 // This is currently the case for all frames on ChromeOS, so logging them |
| 275 // would be spammy, and triggering overuse would be wrong. | 317 // would be spammy, and triggering overuse would be wrong. |
| 276 // https://crbug.com/350106 | 318 // https://crbug.com/350106 |
| 277 while (!frame_timing_.empty()) { | 319 while (!frame_timing_.empty()) { |
| 278 FrameTiming timing = frame_timing_.front(); | 320 FrameTiming timing = frame_timing_.front(); |
| 279 if (now - timing.capture_ms < kEncodingTimeMeasureWindowMs) | 321 if (time_sent_in_ms - timing.capture_ms < kEncodingTimeMeasureWindowMs) |
| 280 break; | 322 break; |
| 281 if (timing.last_send_ms != -1) { | 323 if (timing.last_send_ms != -1) { |
| 282 int encode_duration_ms = | 324 int encode_duration_ms = |
| 283 static_cast<int>(timing.last_send_ms - timing.capture_ms); | 325 static_cast<int>(timing.last_send_ms - timing.capture_ms); |
| 284 if (encoder_timing_) { | 326 if (encoder_timing_) { |
| 285 encoder_timing_->OnEncodeTiming(timing.capture_ntp_ms, | 327 encoder_timing_->OnEncodeTiming(timing.capture_ntp_ms, |
| 286 encode_duration_ms); | 328 encode_duration_ms); |
| 287 } | 329 } |
| 288 if (last_processed_capture_time_ms_ != -1) { | 330 if (last_processed_capture_time_ms_ != -1) { |
| 289 int64_t diff_ms = timing.capture_ms - last_processed_capture_time_ms_; | 331 int64_t diff_ms = timing.capture_ms - last_processed_capture_time_ms_; |
| 290 usage_->AddSample(encode_duration_ms, diff_ms); | 332 usage_->AddSample(encode_duration_ms, diff_ms); |
| 291 } | 333 } |
| 292 last_processed_capture_time_ms_ = timing.capture_ms; | 334 last_processed_capture_time_ms_ = timing.capture_ms; |
| 293 EncodedFrameTimeMeasured(encode_duration_ms); | 335 EncodedFrameTimeMeasured(encode_duration_ms); |
| 294 } | 336 } |
| 295 frame_timing_.pop_front(); | 337 frame_timing_.pop_front(); |
| 296 } | 338 } |
| 297 } | 339 } |
| 298 | 340 |
| 299 void OveruseFrameDetector::Process() { | 341 void OveruseFrameDetector::CheckForOveruse() { |
| 300 RTC_DCHECK(processing_thread_.CalledOnValidThread()); | 342 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_); |
| 343 ++num_process_times_; | |
| 344 if (num_process_times_ <= options_.min_process_count || !metrics_) | |
| 345 return; | |
| 301 | 346 |
| 302 int64_t now = clock_->TimeInMilliseconds(); | 347 int64_t now = clock_->TimeInMilliseconds(); |
| 303 | 348 CpuOveruseMetrics current_metrics = *metrics_; |
|
åsapersson
2016/08/24 08:59:04
remove current_metrics and use metrics_ instead?
perkj_webrtc
2016/09/01 10:03:29
Done.
| |
| 304 // Used to protect against Process() being called too often. | |
| 305 if (now < next_process_time_ms_) | |
| 306 return; | |
| 307 | |
| 308 next_process_time_ms_ = now + kProcessIntervalMs; | |
| 309 | |
| 310 CpuOveruseMetrics current_metrics; | |
| 311 { | |
| 312 rtc::CritScope cs(&crit_); | |
| 313 ++num_process_times_; | |
| 314 if (num_process_times_ <= options_.min_process_count || !metrics_) | |
| 315 return; | |
| 316 | |
| 317 current_metrics = *metrics_; | |
| 318 } | |
| 319 | 349 |
| 320 if (IsOverusing(current_metrics)) { | 350 if (IsOverusing(current_metrics)) { |
| 321 // If the last thing we did was going up, and now have to back down, we need | 351 // If the last thing we did was going up, and now have to back down, we need |
| 322 // to check if this peak was short. If so we should back off to avoid going | 352 // to check if this peak was short. If so we should back off to avoid going |
| 323 // back and forth between this load, the system doesn't seem to handle it. | 353 // back and forth between this load, the system doesn't seem to handle it. |
| 324 bool check_for_backoff = last_rampup_time_ms_ > last_overuse_time_ms_; | 354 bool check_for_backoff = last_rampup_time_ms_ > last_overuse_time_ms_; |
| 325 if (check_for_backoff) { | 355 if (check_for_backoff) { |
| 326 if (now - last_rampup_time_ms_ < kStandardRampUpDelayMs || | 356 if (now - last_rampup_time_ms_ < kStandardRampUpDelayMs || |
| 327 num_overuse_detections_ > kMaxOverusesBeforeApplyRampupDelay) { | 357 num_overuse_detections_ > kMaxOverusesBeforeApplyRampupDelay) { |
| 328 // Going up was not ok for very long, back off. | 358 // Going up was not ok for very long, back off. |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 353 int rampup_delay = | 383 int rampup_delay = |
| 354 in_quick_rampup_ ? kQuickRampUpDelayMs : current_rampup_delay_ms_; | 384 in_quick_rampup_ ? kQuickRampUpDelayMs : current_rampup_delay_ms_; |
| 355 | 385 |
| 356 LOG(LS_VERBOSE) << " Frame stats: " | 386 LOG(LS_VERBOSE) << " Frame stats: " |
| 357 << " encode usage " << current_metrics.encode_usage_percent | 387 << " encode usage " << current_metrics.encode_usage_percent |
| 358 << " overuse detections " << num_overuse_detections_ | 388 << " overuse detections " << num_overuse_detections_ |
| 359 << " rampup delay " << rampup_delay; | 389 << " rampup delay " << rampup_delay; |
| 360 } | 390 } |
| 361 | 391 |
| 362 bool OveruseFrameDetector::IsOverusing(const CpuOveruseMetrics& metrics) { | 392 bool OveruseFrameDetector::IsOverusing(const CpuOveruseMetrics& metrics) { |
| 393 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_); | |
| 363 if (metrics.encode_usage_percent >= | 394 if (metrics.encode_usage_percent >= |
| 364 options_.high_encode_usage_threshold_percent) { | 395 options_.high_encode_usage_threshold_percent) { |
| 365 ++checks_above_threshold_; | 396 ++checks_above_threshold_; |
| 366 } else { | 397 } else { |
| 367 checks_above_threshold_ = 0; | 398 checks_above_threshold_ = 0; |
| 368 } | 399 } |
| 369 return checks_above_threshold_ >= options_.high_threshold_consecutive_count; | 400 return checks_above_threshold_ >= options_.high_threshold_consecutive_count; |
| 370 } | 401 } |
| 371 | 402 |
| 372 bool OveruseFrameDetector::IsUnderusing(const CpuOveruseMetrics& metrics, | 403 bool OveruseFrameDetector::IsUnderusing(const CpuOveruseMetrics& metrics, |
| 373 int64_t time_now) { | 404 int64_t time_now) { |
| 405 RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_); | |
| 374 int delay = in_quick_rampup_ ? kQuickRampUpDelayMs : current_rampup_delay_ms_; | 406 int delay = in_quick_rampup_ ? kQuickRampUpDelayMs : current_rampup_delay_ms_; |
| 375 if (time_now < last_rampup_time_ms_ + delay) | 407 if (time_now < last_rampup_time_ms_ + delay) |
| 376 return false; | 408 return false; |
| 377 | 409 |
| 378 return metrics.encode_usage_percent < | 410 return metrics.encode_usage_percent < |
| 379 options_.low_encode_usage_threshold_percent; | 411 options_.low_encode_usage_threshold_percent; |
| 380 } | 412 } |
| 381 } // namespace webrtc | 413 } // namespace webrtc |
| OLD | NEW |