Constantin Kaplinsky | b30ae7f | 2006-05-25 05:04:46 +0000 | [diff] [blame] | 1 | /* Copyright (C) 2006 Constantin Kaplinsky. All Rights Reserved. |
| 2 | * |
| 3 | * This is free software; you can redistribute it and/or modify |
| 4 | * it under the terms of the GNU General Public License as published by |
| 5 | * the Free Software Foundation; either version 2 of the License, or |
| 6 | * (at your option) any later version. |
| 7 | * |
| 8 | * This software is distributed in the hope that it will be useful, |
| 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 11 | * GNU General Public License for more details. |
| 12 | * |
| 13 | * You should have received a copy of the GNU General Public License |
| 14 | * along with this software; if not, write to the Free Software |
| 15 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
| 16 | * USA. |
| 17 | */ |
| 18 | |
| 19 | // |
| 20 | // PollingScheduler class implementation. |
| 21 | // |
| 22 | |
| 23 | #include <string.h> |
| 24 | #include <stdlib.h> |
| 25 | |
| 26 | #ifdef DEBUG |
| 27 | #include <stdio.h> |
| 28 | #endif |
| 29 | |
| 30 | #include <x0vncserver/PollingScheduler.h> |
| 31 | |
| 32 | PollingScheduler::PollingScheduler(int interval, int maxload) |
| 33 | { |
| 34 | setParameters(interval, maxload); |
| 35 | reset(); |
| 36 | } |
| 37 | |
| 38 | void PollingScheduler::setParameters(int interval, int maxload) |
| 39 | { |
| 40 | m_interval = interval; |
| 41 | m_maxload = maxload; |
| 42 | |
| 43 | if (m_interval < 0) { |
| 44 | m_interval = 0; |
| 45 | } |
| 46 | if (m_maxload < 1) { |
| 47 | m_maxload = 1; |
| 48 | } else if (m_maxload > 100) { |
| 49 | m_maxload = 100; |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | void PollingScheduler::reset() |
| 54 | { |
| 55 | m_initialState = true; |
| 56 | } |
| 57 | |
Constantin Kaplinsky | 813dbb4 | 2006-12-05 08:03:18 +0000 | [diff] [blame] | 58 | bool PollingScheduler::isRunning() |
| 59 | { |
| 60 | return !m_initialState; |
| 61 | } |
| 62 | |
Constantin Kaplinsky | b30ae7f | 2006-05-25 05:04:46 +0000 | [diff] [blame] | 63 | void PollingScheduler::newPass() |
| 64 | { |
| 65 | TimeMillis timeNow; |
| 66 | |
| 67 | if (m_initialState) { |
| 68 | |
| 69 | // First polling pass: initialize statistics. |
| 70 | m_initialState = false; |
| 71 | m_ratedDuration = 0; |
| 72 | m_sleeping = 0; |
| 73 | memset(m_errors, 0, sizeof(m_errors)); |
| 74 | m_errorSum = 0; |
| 75 | m_errorAbsSum = 0; |
| 76 | memset(m_durations, 0, sizeof(m_durations)); |
| 77 | m_durationSum = 0; |
| 78 | memset(m_slept, 0, sizeof(m_slept)); |
| 79 | m_sleptSum = 0; |
| 80 | m_idx = 0; |
| 81 | m_count = 0; |
| 82 | |
| 83 | } else { |
| 84 | |
| 85 | // Stop sleeping if not yet. |
| 86 | if (m_sleeping) |
| 87 | sleepFinished(); |
| 88 | |
| 89 | // Update statistics on sleeping time and total pass duration. |
| 90 | int duration = timeNow.diffFrom(m_passStarted); |
| 91 | |
| 92 | int oldest = m_durations[m_idx]; |
| 93 | m_durations[m_idx] = duration; |
| 94 | m_durationSum = m_durationSum - oldest + duration; |
| 95 | |
| 96 | oldest = m_slept[m_idx]; |
| 97 | m_slept[m_idx] = m_sleptThisPass; |
| 98 | m_sleptSum = m_sleptSum - oldest + m_sleptThisPass; |
| 99 | |
| 100 | // Compute and save the difference between actual and planned time. |
| 101 | int newError = duration - m_interval; |
| 102 | oldest = m_errors[m_idx]; |
| 103 | m_errors[m_idx] = newError; |
| 104 | m_errorSum = m_errorSum - oldest + newError; |
| 105 | m_errorAbsSum = m_errorAbsSum - abs(oldest) + abs(newError); |
| 106 | |
| 107 | // |
| 108 | // Below is the most important part. |
| 109 | // Compute desired duration of the upcoming polling pass. |
| 110 | // |
| 111 | |
| 112 | // Estimation based on keeping up constant interval. |
| 113 | m_ratedDuration = m_interval - m_errorSum / 2; |
| 114 | |
| 115 | // Estimations based on keeping up desired CPU load. |
| 116 | int optimalLoadDuration1 = 0; |
| 117 | int optimalLoadDuration8 = 0; |
| 118 | int optimalLoadDuration = 0; |
| 119 | |
| 120 | if (m_count > 4) { |
| 121 | // Estimation 1 (use previous pass statistics). |
| 122 | optimalLoadDuration1 = |
| 123 | ((duration - m_sleptThisPass) * 100 + m_maxload/2) / m_maxload; |
| 124 | |
| 125 | if (m_count > 16) { |
| 126 | // Estimation 2 (use history of 8 previous passes). |
| 127 | optimalLoadDuration8 = |
| 128 | ((m_durationSum - m_sleptSum) * 900 + m_maxload*4) / (m_maxload*8) |
| 129 | - m_durationSum; |
| 130 | // Mix the above two giving more priority to the first. |
| 131 | optimalLoadDuration = |
| 132 | (2 * optimalLoadDuration1 + optimalLoadDuration8) / 3; |
| 133 | } else { |
| 134 | optimalLoadDuration = optimalLoadDuration1; |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | #ifdef DEBUG |
| 139 | fprintf(stderr, "<est %3d,%3d,%d>\t", |
| 140 | m_ratedDuration, optimalLoadDuration1, optimalLoadDuration8); |
| 141 | #endif |
| 142 | |
| 143 | // Choose final estimation. |
| 144 | if (m_ratedDuration < optimalLoadDuration) { |
| 145 | m_ratedDuration = optimalLoadDuration; |
| 146 | } |
| 147 | if (m_ratedDuration < 0) { |
| 148 | m_ratedDuration = 0; |
| 149 | } else if (m_ratedDuration > 500 && m_interval <= 100) { |
| 150 | m_ratedDuration = 500; |
| 151 | } else if (m_ratedDuration > 1000) { |
| 152 | m_ratedDuration = 1000; |
| 153 | } |
| 154 | |
| 155 | #ifdef DEBUG |
| 156 | fprintf(stderr, "<final est %3d>\t", m_ratedDuration); |
| 157 | #endif |
| 158 | |
| 159 | // Update ring buffer indexer (8 elements per each arrays). |
| 160 | m_idx = (m_idx + 1) & 7; |
| 161 | |
| 162 | // Update pass counter. |
| 163 | m_count++; |
| 164 | |
| 165 | } |
| 166 | |
| 167 | m_passStarted = timeNow; |
| 168 | m_sleptThisPass = 0; |
| 169 | } |
| 170 | |
| 171 | void PollingScheduler::sleepStarted() |
| 172 | { |
| 173 | if (m_initialState || m_sleeping) |
| 174 | return; |
| 175 | |
| 176 | m_sleepStarted.update(); |
| 177 | |
| 178 | m_sleeping = true; |
| 179 | } |
| 180 | |
| 181 | void PollingScheduler::sleepFinished() |
| 182 | { |
| 183 | if (m_initialState || !m_sleeping) |
| 184 | return; |
| 185 | |
| 186 | TimeMillis timeNow; |
| 187 | m_sleptThisPass += timeNow.diffFrom(m_sleepStarted); |
| 188 | |
| 189 | m_sleeping = false; |
| 190 | } |
| 191 | |
| 192 | int PollingScheduler::millisRemaining() const |
| 193 | { |
| 194 | if (m_initialState) |
| 195 | return 0; |
| 196 | |
| 197 | TimeMillis timeNow; |
| 198 | int elapsed = timeNow.diffFrom(m_passStarted); |
| 199 | |
| 200 | if (elapsed > m_ratedDuration) |
| 201 | return 0; |
| 202 | |
| 203 | return (m_ratedDuration - elapsed); |
| 204 | } |
| 205 | |
| 206 | bool PollingScheduler::goodTimeToPoll() const |
| 207 | { |
| 208 | if (m_initialState) |
| 209 | return true; |
| 210 | |
| 211 | // Average error (per 8 elements in the ring buffer). |
| 212 | int errorAvg = (m_errorAbsSum + 4) / 8; |
| 213 | |
| 214 | // It's ok to poll earlier if new error is no more than half-average. |
| 215 | return (millisRemaining() <= errorAvg / 2); |
| 216 | } |