From: Constantin Kaplinsky Date: Fri, 17 Feb 2006 09:30:21 +0000 (+0000) Subject: Major improvement of the CPU load optimization implementation. The X-Git-Tag: v0.0.90~384^2~339 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=e179b5a0975d336ff98f7dd9b75c5febf749b464;p=tigervnc.git Major improvement of the CPU load optimization implementation. The CPUMonitor class is not used any more, that work is implemented in the PollingScheduler class instead. New implementation solves the problem of "random" sudden changes of CPU load that could be seen in previous versions. Also, measurement method for CPU utilization has been changed, the old one did not count CPU utilization increased by other processes. git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@496 3789f03b-4d11-0410-bbf8-ca57d06f2519 --- diff --git a/x0vncserver/CPUMonitor.cxx b/x0vncserver/CPUMonitor.cxx deleted file mode 100644 index be2214fe..00000000 --- a/x0vncserver/CPUMonitor.cxx +++ /dev/null @@ -1,102 +0,0 @@ -/* Copyright (C) 2005 Constantin Kaplinsky. All Rights Reserved. - * - * This is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This software is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this software; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, - * USA. - */ - -// -// CPUMonitor.cxx -// - -#include - -CPUMonitor::CPUMonitor(int optimalLevel, int updatePeriod) -{ - setLevel(optimalLevel); - setPeriod(updatePeriod); - update(); -} - -void CPUMonitor::setLevel(int optimalLevel) -{ - m_optimalLevel = optimalLevel; - if (m_optimalLevel < 0) { - m_optimalLevel = 0; - } else if (m_optimalLevel > 100) { - m_optimalLevel = 100; - } -} - -void CPUMonitor::setPeriod(int updatePeriod) -{ - m_updatePeriod = (updatePeriod > 10) ? updatePeriod : 10; -} - -void CPUMonitor::update() -{ - getClock(&m_savedTime, &m_savedClock); -} - -int CPUMonitor::check() -{ - TimeMillis timeNow; - clock_t clockNow; - getClock(&timeNow, &clockNow); - - int coeff = 100; // 100% means no changes. - - if (m_savedClock != (clock_t)-1 && clockNow != (clock_t)-1) { - - // Find out how much real time has been elapsed (in milliseconds). - int timeDiff = timeNow.diffFrom(m_savedTime); - - if (timeDiff < m_updatePeriod) { - // Measuring CPU usage is problematic in this case. So return - // 100 and do not update saved time and clock numbers. - return coeff; - } - - // Calculate how much processor time has been consumed (in milliseconds). - unsigned int clockDiff = (unsigned int)(clockNow - m_savedClock); - clockDiff /= (CLOCKS_PER_SEC / 1000); - - // Get actual CPU usage and convert optimal level (to 1/1000). - int realUsage = (clockDiff * 1000 + timeDiff/2) / timeDiff; - int optimalUsage = m_optimalLevel * 10; - - // Compute correction coefficient (in percents). - if (realUsage != 0) { - coeff = (optimalUsage * 100 + realUsage/2) / realUsage; - } else { - coeff = 10000; - } - - } - - // Update saved time and clock numbers. - m_savedTime = timeNow; - m_savedClock = clockNow; - - return coeff; -} - -void CPUMonitor::getClock(TimeMillis *tm, clock_t *clk) -{ - if (tm->update()) { - *clk = clock(); - } else { - *clk = (clock_t)-1; - } -} diff --git a/x0vncserver/CPUMonitor.h b/x0vncserver/CPUMonitor.h deleted file mode 100644 index 0421e515..00000000 --- a/x0vncserver/CPUMonitor.h +++ /dev/null @@ -1,78 +0,0 @@ -/* Copyright (C) 2005 Constantin Kaplinsky. All Rights Reserved. - * - * This is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This software is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this software; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, - * USA. - */ - -// -// CPUMonitor.h -// - -#ifndef __CPUMONITOR_H__ -#define __CPUMONITOR_H__ - -#include - -#include - -class CPUMonitor { - -public: - - CPUMonitor(int optimalLevel = 50, int updatePeriod = 1000); - - // - // Optimal level is the CPU utilization level to maintain, in - // percents. - // - void setLevel(int optimalLevel); - - // - // Update period is the minimum time in milliseconds that has to - // pass after update() or check() call, before next check() call - // will return meaningful value. - // - void setPeriod(int updatePeriod); - - // - // Save current time and CPU clock, to use in the next check() call. - // - void update(); - - // - // This method calculates recent CPU utilization and returns a - // percentage factor which would convert current CPU usage into the - // optimal value. For example, if the optimal level was set to 40% - // and the real value was 50%, the function will return 80, because - // 50 * 80% == 40. - // - // If the CPU utilization cannot be measured, this function returns - // 100. This may be the case on the first call to check(), or if the - // time period between two successive check() calls was too small. - // - int check(); - -protected: - - static void getClock(TimeMillis *tm, clock_t *clk); - - int m_optimalLevel; - int m_updatePeriod; - TimeMillis m_savedTime; - clock_t m_savedClock; - -}; - -#endif // __CPUMONITOR_H__ diff --git a/x0vncserver/Makefile.in b/x0vncserver/Makefile.in index f9a2ad76..b6bd0bab 100644 --- a/x0vncserver/Makefile.in +++ b/x0vncserver/Makefile.in @@ -1,6 +1,6 @@ -SRCS = Image.cxx CPUMonitor.cxx TimeMillis.cxx PollingManager.cxx \ - PollingScheduler.cxx x0vncserver.cxx +SRCS = Image.cxx TimeMillis.cxx PollingScheduler.cxx PollingManager.cxx \ + x0vncserver.cxx OBJS = $(SRCS:.cxx=.o) diff --git a/x0vncserver/PollingScheduler.cxx b/x0vncserver/PollingScheduler.cxx index f3263513..af597df4 100644 --- a/x0vncserver/PollingScheduler.cxx +++ b/x0vncserver/PollingScheduler.cxx @@ -20,17 +20,30 @@ // PollingScheduler class implementation. // +#include +#include + #include -PollingScheduler::PollingScheduler(int interval) +PollingScheduler::PollingScheduler(int interval, int maxload) { - setInterval(interval); + setParameters(interval, maxload); reset(); } -void PollingScheduler::setInterval(int interval) +void PollingScheduler::setParameters(int interval, int maxload) { m_interval = interval; + m_maxload = maxload; + + if (m_interval < 0) { + m_interval = 0; + } + if (m_maxload < 1) { + m_maxload = 1; + } else if (m_maxload > 100) { + m_maxload = 100; + } } void PollingScheduler::reset() @@ -40,8 +53,93 @@ void PollingScheduler::reset() void PollingScheduler::newPass() { - m_passStarted.update(); - m_initialState = false; + TimeMillis timeNow; + + if (m_initialState) { + + // First polling pass: initialize statistics. + m_initialState = false; + m_ratedDuration = 0; + m_sleeping = 0; + memset(m_errors, 0, sizeof(m_errors)); + m_errorSum = 0; + m_errorAbsSum = 0; + memset(m_durations, 0, sizeof(m_durations)); + m_durationSum = 0; + memset(m_slept, 0, sizeof(m_slept)); + m_sleptSum = 0; + m_idx = 0; + + } else { + + // Stop sleeping if not yet. + if (m_sleeping) + sleepFinished(); + + // Update statistics on sleeping time and total pass duration. + int duration = timeNow.diffFrom(m_passStarted); + + int oldest = m_durations[m_idx]; + m_durations[m_idx] = duration; + m_durationSum = m_durationSum - oldest + duration; + + oldest = m_slept[m_idx]; + m_slept[m_idx] = m_sleptThisPass; + m_sleptSum = m_sleptSum - oldest + m_sleptThisPass; + + // Compute and save the difference between actual and planned time. + int newError = duration - m_interval; + oldest = m_errors[m_idx]; + m_errors[m_idx] = newError; + m_errorSum = m_errorSum - oldest + newError; + m_errorAbsSum = m_errorAbsSum - abs(oldest) + abs(newError); + + // + // Here is the most important part. + // Compute desired duration of the upcoming polling pass. + // + m_ratedDuration = m_interval - m_errorSum; + + int optimalLoadDuration = + ((m_durationSum - m_sleptSum) * 900 + m_maxload * 4) / (m_maxload * 8) + - m_durationSum; + + if (m_ratedDuration < optimalLoadDuration) { + m_ratedDuration = optimalLoadDuration; + } + + if (m_ratedDuration < 0) { + m_ratedDuration = 0; + } + + // Update ring buffer indexer (8 elements in the arrays). + m_idx = (m_idx + 1) & 7; + + } + + m_passStarted = timeNow; + m_sleptThisPass = 0; +} + +void PollingScheduler::sleepStarted() +{ + if (m_initialState || m_sleeping) + return; + + m_sleepStarted.update(); + + m_sleeping = true; +} + +void PollingScheduler::sleepFinished() +{ + if (m_initialState || !m_sleeping) + return; + + TimeMillis timeNow; + m_sleptThisPass += timeNow.diffFrom(m_sleepStarted); + + m_sleeping = false; } int PollingScheduler::millisRemaining() const @@ -52,13 +150,20 @@ int PollingScheduler::millisRemaining() const TimeMillis timeNow; int elapsed = timeNow.diffFrom(m_passStarted); - if (elapsed > m_interval) + if (elapsed > m_ratedDuration) return 0; - return (m_interval - elapsed); + return (m_ratedDuration - elapsed); } bool PollingScheduler::goodTimeToPoll() const { - return (millisRemaining() == 0); + if (m_initialState) + return true; + + // Average error (per 8 elements in the ring buffer). + int errorAvg = (m_errorAbsSum + 4) / 8; + + // It's ok to poll earlier if new error is no more than half-average. + return (millisRemaining() <= errorAvg / 2); } diff --git a/x0vncserver/PollingScheduler.h b/x0vncserver/PollingScheduler.h index 97376857..025139a1 100644 --- a/x0vncserver/PollingScheduler.h +++ b/x0vncserver/PollingScheduler.h @@ -18,9 +18,10 @@ // // PollingScheduler class. It is used for deciding when to start new -// polling pass, and how much time it is ok to wait before starting. -// PollingScheduler is provided a desired polling interval and watches -// for actual intervals between past polling cycles. +// polling pass, and how much time it is ok to sleep before starting. +// PollingScheduler is given a desired polling interval, but it can +// add time between polling passes if needed for satisfying processor +// usage limitation. // #ifndef __POLLINGSCHEDULER_H__ @@ -32,10 +33,10 @@ class PollingScheduler { public: - PollingScheduler(int interval); + PollingScheduler(int interval, int maxload = 50); - // Set desired polling interval. - void setInterval(int interval); + // Set polling parameters. + void setParameters(int interval, int maxload = 50); // Reset the object into the initial state (no polling performed). void reset(); @@ -43,6 +44,10 @@ public: // Tell the scheduler that new polling pass is just being started. void newPass(); + // Inform the scheduler about times when we sleep. + void sleepStarted(); + void sleepFinished(); + // This function estimates time remaining before new polling pass. int millisRemaining() const; @@ -51,10 +56,40 @@ public: protected: - bool m_initialState; + // Parameters. int m_interval; + int m_maxload; + + // This boolean flag is true when we do not poll the screen. + bool m_initialState; + + // Time stamp saved on starting current polling pass. TimeMillis m_passStarted; + // Desired duration of current polling pass. + int m_ratedDuration; + + // These are for measuring sleep time in current pass. + TimeMillis m_sleepStarted; + bool m_sleeping; + int m_sleptThisPass; + + // Ring buffer for tracking past timing errors. + int m_errors[8]; + int m_errorSum; + int m_errorAbsSum; + + // Ring buffer for tracking total pass durations (work + sleep). + int m_durations[8]; + int m_durationSum; + + // Ring buffer for tracking past sleep times. + int m_slept[8]; + int m_sleptSum; + + // Indexer for all ring buffers. + int m_idx; + }; #endif // __POLLINGSCHEDULER_H__ diff --git a/x0vncserver/x0vncserver.cxx b/x0vncserver/x0vncserver.cxx index d179d943..30e54162 100644 --- a/x0vncserver/x0vncserver.cxx +++ b/x0vncserver/x0vncserver.cxx @@ -43,7 +43,6 @@ #include #include -#include #include using namespace rfb; @@ -324,48 +323,6 @@ static void usage() exit(1); } -// -// Adjust polling cycle to satisfy MaxProcessorUsage setting. -// - -static void adjustPollingCycle(int *cycle, CPUMonitor *mon) -{ - int coeff = mon->check(); - if (coeff < 90 || coeff > 110) { - int oldValue = *cycle; - int newValue = (oldValue * 100 + coeff/2) / coeff; - - // Correct sudden changes. - if (newValue < oldValue / 2) { - newValue = oldValue / 2; - } else if (newValue > oldValue * 2) { - newValue = oldValue * 2; - } - - // Compute upper limit. - int upperLimit = (int)pollingCycle * 32; - if (upperLimit < 100) { - upperLimit = 100; - } else if (upperLimit > 1000) { - upperLimit = 1000; - } - - // Limit lower and upper bounds. - if (newValue < (int)pollingCycle) { - newValue = (int)pollingCycle; - } else if (newValue > upperLimit) { - newValue = upperLimit; - } - - if (newValue != oldValue) { - *cycle = newValue; -#ifdef DEBUG - fprintf(stderr, "\t[new cycle %dms]\n", newValue); -#endif - } - } -} - int main(int argc, char** argv) { initStdIOLoggers(); @@ -415,9 +372,7 @@ int main(int argc, char** argv) if (strlen(hostsFile.getData()) != 0) listener.setFilter(&fileTcpFilter); - CPUMonitor cpumon((int)maxProcessorUsage, 1000); - int dynPollingCycle = (int)pollingCycle; - PollingScheduler sched(dynPollingCycle); + PollingScheduler sched((int)pollingCycle, (int)maxProcessorUsage); fd_set rfds; struct timeval tv; @@ -451,7 +406,10 @@ int main(int argc, char** argv) } tv.tv_sec = 0; + sched.sleepStarted(); int n = select(FD_SETSIZE, &rfds, 0, 0, &tv); + sched.sleepFinished(); + if (n < 0) { if (errno == EINTR) { vlog.debug("interrupted select() system call"); @@ -465,7 +423,6 @@ int main(int argc, char** argv) Socket* sock = listener.accept(); if (sock) { server.addClient(sock); - cpumon.update(); // count time from now } else { vlog.status("Client connection rejected"); } @@ -486,8 +443,6 @@ int main(int argc, char** argv) server.checkTimeouts(); if (sched.goodTimeToPoll()) { - adjustPollingCycle(&dynPollingCycle, &cpumon); - sched.setInterval(dynPollingCycle); sched.newPass(); desktop.poll(); }