+++ /dev/null
-/* 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 <x0vncserver/CPUMonitor.h>
-
-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;
- }
-}
+++ /dev/null
-/* 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 <time.h>
-
-#include <x0vncserver/TimeMillis.h>
-
-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__
-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)
// PollingScheduler class implementation.
//
+#include <string.h>
+#include <stdlib.h>
+
#include <x0vncserver/PollingScheduler.h>
-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()
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
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);
}
//
// 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__
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();
// 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;
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__
#include <x0vncserver/Image.h>
#include <x0vncserver/PollingManager.h>
-#include <x0vncserver/CPUMonitor.h>
#include <x0vncserver/PollingScheduler.h>
using namespace rfb;
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();
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;
}
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");
Socket* sock = listener.accept();
if (sock) {
server.addClient(sock);
- cpumon.update(); // count time from now
} else {
vlog.status("Client connection rejected");
}
server.checkTimeouts();
if (sched.goodTimeToPoll()) {
- adjustPollingCycle(&dynPollingCycle, &cpumon);
- sched.setInterval(dynPollingCycle);
sched.newPass();
desktop.poll();
}