Skip to content

Commit 33b6c12

Browse files
committed
MB-29928: Reset PID when configuration changes
Update the PID so that it will recheck the configuration against its internal state. Whenever one of the terms changes, the PID resets. Change-Id: I247f6d9d67fd6ef4c2484077a0fe1091dd22ac6e Reviewed-on: http://review.couchbase.org/c/kv_engine/+/156244 Tested-by: Build Bot <[email protected]> Reviewed-by: Dave Rigby <[email protected]>
1 parent f04d4f9 commit 33b6c12

File tree

9 files changed

+167
-19
lines changed

9 files changed

+167
-19
lines changed

engines/ep/configuration.json

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@
356356
"defragmenter_mode" : {
357357
"default": "auto_pid",
358358
"descr": "Determines how the defragmenter controls its sleep interval. When static defragmenter_interval is used. When auto_linear, scale the sleep time using a scored defragmentation when it falls between defragmenter_auto_lower_trigger and defragmenter_auto_upper_trigger. When auto_pid use a PID controller to computer reductions in the sleep interval when scored fragmentation is above defragmenter_auto_lower_trigger.",
359-
"dynamic": false,
359+
"dynamic": true,
360360
"type": "std::string",
361361
"validator": {
362362
"enum": [
@@ -369,49 +369,49 @@
369369
"defragmenter_auto_lower_threshold" : {
370370
"default": "0.07",
371371
"descr": "When mode is not static and scored fragmentation is above this value, a sleep time between defragmenter_auto_min_sleep and defragmenter_auto_max_sleep will be used",
372-
"dynamic": false,
372+
"dynamic": true,
373373
"type": "float"
374374
},
375375
"defragmenter_auto_upper_threshold" : {
376376
"default": "0.25",
377377
"descr": "When mode is auto_linear and scored fragmentation is above this value, the defragmenter will use defragmenter_auto_min_sleep",
378-
"dynamic": false,
378+
"dynamic": true,
379379
"type": "float"
380380
},
381381
"defragmenter_auto_max_sleep" : {
382382
"default": "10.0",
383383
"descr": "The maximum sleep that the auto controller can set",
384-
"dynamic": false,
384+
"dynamic": true,
385385
"type": "float"
386386
},
387387
"defragmenter_auto_min_sleep" : {
388388
"default": "0.0",
389389
"descr": "The minimum sleep that the auto controller can set",
390-
"dynamic": false,
390+
"dynamic": true,
391391
"type": "float"
392392
},
393393
"defragmenter_auto_pid_p" : {
394394
"default": "0.3",
395395
"descr": "The p term for the PID controller",
396-
"dynamic": false,
396+
"dynamic": true,
397397
"type": "float"
398398
},
399399
"defragmenter_auto_pid_i" : {
400400
"default": "0.0000197",
401401
"descr": "The i term for the PID controller",
402-
"dynamic": false,
402+
"dynamic": true,
403403
"type": "float"
404404
},
405405
"defragmenter_auto_pid_d" : {
406406
"default": "0.0",
407407
"descr": "The d term for the PID controller",
408-
"dynamic": false,
408+
"dynamic": true,
409409
"type": "float"
410410
},
411411
"defragmenter_auto_pid_dt" : {
412412
"default": "30000",
413413
"descr": "The dt (interval) term for the PID controller. Value represents milliseconds",
414-
"dynamic": false,
414+
"dynamic": true,
415415
"type": "size_t"
416416
},
417417
"durability_timeout_task_interval": {

engines/ep/management/cbepctl

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,8 +234,26 @@ Available params for "set":
234234
upgrade a NormalWrite to SyncWrite.
235235
defragmenter_enabled - Enable or disable the defragmenter
236236
(true/false).
237+
defragmenter_mode - The mode the defragmenter is in static, auto_linear or auto_pid
237238
defragmenter_interval - How often defragmenter task should be run
238-
(in seconds).
239+
when mode is static (in seconds).
240+
defragmenter_auto_lower_threshold - When the mode is not static a 'scored'
241+
threshold for when sleep time is recalculated.
242+
(parameter is floating point)
243+
defragmenter_auto_upper_threshold - When the mode is auto_linear and 'scored' fragmentation
244+
exceeds this, defragmenter_auto_max_sleep is used.
245+
(parameter is floating point)
246+
defragmenter_auto_max_sleep - The minimum sleep time when mode is not
247+
static (fractional seconds, e.g. 0.2)
248+
defragmenter_auto_min_sleep - The maximum sleep time when mode is not
249+
static (fractional seconds, e.g. 8.5)
250+
defragmenter_auto_pid_p - The P term for when mode is auto_pid
251+
(parameter is floating point)
252+
defragmenter_auto_pid_i - The I term for when mode is auto_pid
253+
(parameter is floating point)
254+
defragmenter_auto_pid_d - The D term for when mode is auto_pid
255+
(parameter is floating point)
256+
defragmenter_auto_pid_dt - The interval term for when mode is auto_pid
239257
defragmenter_age_threshold - How old (measured in number of defragmenter
240258
passes) must a document be to be considered
241259
for defragmentation.

engines/ep/src/defragmenter.cc

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,22 @@ DefragmenterTask::DefragmenterTask(EventuallyPersistentEngine* e,
3131
engine->getConfiguration().getDefragmenterAutoPidI(),
3232
engine->getConfiguration().getDefragmenterAutoPidD(),
3333
std::chrono::milliseconds{
34-
engine->getConfiguration().getDefragmenterAutoPidDt()}) {
34+
engine->getConfiguration().getDefragmenterAutoPidDt()}),
35+
pidReset([this](PIDControllerImpl& pid) {
36+
const auto& conf = engine->getConfiguration();
37+
// Read and compare the PID against the engine config
38+
auto sp = conf.getDefragmenterAutoLowerThreshold();
39+
auto p = conf.getDefragmenterAutoPidP();
40+
auto i = conf.getDefragmenterAutoPidI();
41+
auto d = conf.getDefragmenterAutoPidD();
42+
auto dt = std::chrono::milliseconds{conf.getDefragmenterAutoPidDt()};
43+
if (sp != pid.getSetPoint() || p != pid.getKp() || i != pid.getKi() ||
44+
d != pid.getKd() || dt != pid.getDt()) {
45+
pid.reset(sp, p, i, d, dt);
46+
return true;
47+
}
48+
return false;
49+
}) {
3550
}
3651

3752
bool DefragmenterTask::run() {
@@ -284,7 +299,7 @@ DefragmenterTask::SleepTimeAndRunState DefragmenterTask::calculateSleepPID(
284299
// it's below the SP. In this case reset and when we go over again begin
285300
// the ramping
286301
if (score < conf.getDefragmenterAutoLowerThreshold()) {
287-
// Reset the PID ready for the next time fragmentation increases
302+
// Reset the PID ready for the next time fragmentation increases.
288303
pid.reset();
289304
return {std::chrono::duration<double>{maxSleep}, false};
290305
}
@@ -305,5 +320,5 @@ DefragmenterTask::SleepTimeAndRunState DefragmenterTask::calculateSleepPID(
305320
}
306321

307322
float DefragmenterTask::stepPid(float pv) {
308-
return pid.step(pv);
323+
return pid.step(pv, pidReset);
309324
}

engines/ep/src/defragmenter.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,4 +206,7 @@ class DefragmenterTask : public GlobalTask {
206206
* PID which is used for sleep calculations when deframenter_mode==auto_pid
207207
*/
208208
PIDController<> pid;
209+
210+
/// A function so we can reset the PID on config changes
211+
std::function<bool(PIDControllerImpl&)> pidReset;
209212
};

engines/ep/src/ep_engine.cc

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,26 @@ cb::engine_errc EventuallyPersistentEngine::setFlushParam(
708708
std::stoull(val));
709709
} else if (key == "defragmenter_run") {
710710
runDefragmenterTask();
711+
} else if (key == "defragmenter_mode") {
712+
getConfiguration().setDefragmenterMode(val);
713+
} else if (key == "defragmenter_auto_lower_threshold") {
714+
getConfiguration().setDefragmenterAutoLowerThreshold(
715+
std::stof(val));
716+
} else if (key == "defragmenter_auto_upper_threshold") {
717+
getConfiguration().setDefragmenterAutoUpperThreshold(
718+
std::stof(val));
719+
} else if (key == "defragmenter_auto_max_sleep") {
720+
getConfiguration().setDefragmenterAutoMaxSleep(std::stof(val));
721+
} else if (key == "defragmenter_auto_min_sleep") {
722+
getConfiguration().setDefragmenterAutoMinSleep(std::stof(val));
723+
} else if (key == "defragmenter_auto_pid_p") {
724+
getConfiguration().setDefragmenterAutoPidP(std::stof(val));
725+
} else if (key == "defragmenter_auto_pid_i") {
726+
getConfiguration().setDefragmenterAutoPidI(std::stof(val));
727+
} else if (key == "defragmenter_auto_pid_d") {
728+
getConfiguration().setDefragmenterAutoPidD(std::stof(val));
729+
} else if (key == "defragmenter_auto_pid_dt") {
730+
getConfiguration().setDefragmenterAutoPidDt(std::stof(val));
711731
} else if (key == "compaction_write_queue_cap") {
712732
getConfiguration().setCompactionWriteQueueCap(std::stoull(val));
713733
} else if (key == "chk_expel_enabled") {

engines/ep/src/pid_controller.cc

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ PIDControllerImpl::PIDControllerImpl(
2323
}
2424

2525
float PIDControllerImpl::step(
26-
float current, std::chrono::time_point<std::chrono::steady_clock> now) {
26+
float current,
27+
std::chrono::time_point<std::chrono::steady_clock> now,
28+
std::function<bool(PIDControllerImpl&)> checkAndMaybeReset) {
2729
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
2830
now - lastStep);
2931

@@ -33,6 +35,10 @@ float PIDControllerImpl::step(
3335
return output;
3436
}
3537

38+
// Invoke this function now which is intended to allow the PID owner to
39+
// reset the PID if configuration has changed.
40+
(void)checkAndMaybeReset(*this);
41+
3642
float error = setPoint - current;
3743

3844
integral += (error * duration.count());
@@ -46,6 +52,19 @@ float PIDControllerImpl::step(
4652
return output;
4753
}
4854

55+
void PIDControllerImpl::reset(float setPoint,
56+
float kp,
57+
float ki,
58+
float kd,
59+
std::chrono::milliseconds dt) {
60+
this->setPoint = setPoint;
61+
this->kp = kp;
62+
this->ki = ki;
63+
this->kd = kd;
64+
this->dt = dt;
65+
reset();
66+
}
67+
4968
void PIDControllerImpl::reset() {
5069
integral = 0;
5170
previousError = 0;

engines/ep/src/pid_controller.h

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#pragma once
1313

1414
#include <chrono>
15+
#include <functional>
1516
#include <iosfwd>
1617

1718
/**
@@ -55,17 +56,57 @@ class PIDControllerImpl {
5556
*
5657
* @param current The current reading of the process variable
5758
* @param now The time point for 'now'
59+
* @param checkAndMaybeReset a function that is called before the PID
60+
* generates a new output. The purpose of this function is to allow
61+
* the caller to examine SP, P, I, D and dt terms and maybe reset.
5862
* @return the PID's computed output
5963
*/
6064
float step(float current,
61-
std::chrono::time_point<std::chrono::steady_clock> now);
65+
std::chrono::time_point<std::chrono::steady_clock> now,
66+
std::function<bool(PIDControllerImpl&)> checkAndMaybeReset);
67+
68+
/**
69+
* Reset the PID state.
70+
* The setPoint, P, I, D and dt are all set to the given values
71+
* The current integral, previousError and output are all set to zero.
72+
*/
73+
void reset(float setPoint,
74+
float kp,
75+
float ki,
76+
float kd,
77+
std::chrono::milliseconds dt);
6278

6379
/**
6480
* Reset the PID state.
6581
* The current integral, previousError and output are all set to zero.
6682
*/
6783
void reset();
6884

85+
/// @return the current P term
86+
float getKp() const {
87+
return kp;
88+
}
89+
90+
/// @return the current I term
91+
float getKi() const {
92+
return ki;
93+
}
94+
95+
/// @return the current D term
96+
float getKd() const {
97+
return kd;
98+
}
99+
100+
/// @return the current Dt term
101+
std::chrono::milliseconds getDt() const {
102+
return dt;
103+
}
104+
105+
/// @return the current set point
106+
float getSetPoint() const {
107+
return setPoint;
108+
}
109+
69110
private:
70111
float kp{0.0};
71112
float ki{0.0};
@@ -101,8 +142,21 @@ class PIDController {
101142
* @param current The current reading of the process variable
102143
* @return the PID's computed output
103144
*/
104-
float step(float current) {
105-
return impl.step(current, Clock::now());
145+
float step(float current,
146+
std::function<bool(PIDControllerImpl&)> checkAndMaybeReset) {
147+
return impl.step(current, Clock::now(), checkAndMaybeReset);
148+
}
149+
150+
/**
151+
* Reset the PID state.
152+
* The current integral, previousError and output are all set to zero.
153+
*/
154+
void reset(float setPoint,
155+
float kp,
156+
float ki,
157+
float kd,
158+
std::chrono::milliseconds dt) {
159+
impl.reset(setPoint, kp, ki, kd, dt);
106160
}
107161

108162
/**
@@ -113,6 +167,11 @@ class PIDController {
113167
impl.reset();
114168
}
115169

170+
bool checkAndMaybeReset(
171+
std::function<bool(PIDControllerImpl&)> checkAndMaybeResetFn) {
172+
return checkAndMaybeResetFn(impl);
173+
}
174+
116175
private:
117176
PIDControllerImpl impl;
118177

engines/ep/tests/module_tests/defragmenter_test.cc

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ class MockDefragmenterTask : public DefragmenterTask {
432432

433433
// override stepPid so we can move time
434434
float stepPid(float pv) override {
435-
return testPid.step(pv);
435+
return testPid.step(pv, pidReset);
436436
}
437437

438438
PIDController<MockDefragmenterTaskClock> testPid;
@@ -490,6 +490,20 @@ TEST_F(DefragmenterTaskTest, autoCalculateSleep_PID) {
490490
state = task->public_calculateSleepPID(cb::FragmentationStats{10, 5000});
491491
EXPECT_EQ(conf.getDefragmenterAutoMinSleep(), state.sleepTime.count());
492492
EXPECT_TRUE(state.runDefragger);
493+
494+
// Finally reconfigure.
495+
// Test the PID spots a config change - let's set P I D to 0, so the PID
496+
// returns 0 on the next step meaning it will now move back to maxSleep
497+
conf.setDefragmenterAutoPidP(0.0);
498+
conf.setDefragmenterAutoPidI(0.0);
499+
conf.setDefragmenterAutoPidD(0.0);
500+
501+
// High fragmentation yet PID is 'disabled' so returns max sleep
502+
state = task->public_calculateSleepPID(cb::FragmentationStats{400, 600});
503+
EXPECT_EQ(conf.getDefragmenterAutoMaxSleep(), state.sleepTime.count());
504+
505+
// The high fragmentation so the defragger should run
506+
EXPECT_TRUE(state.runDefragger);
493507
}
494508

495509
TEST_F(DefragmenterTaskTest, autoCalculateSleep) {

engines/ep/tools/pid_runner.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ int main(int argc, char* argv[]) {
134134
PIDController<FauxClock> pid{
135135
setpoint, p, i, d, std::chrono::milliseconds{interval}};
136136
for (int step = 0; step < steps; step++) {
137-
auto c = pid.step(pv);
137+
auto c = pid.step(pv, [](PIDControllerImpl&) { return false; });
138138

139139
// Compute the sleep like the defragger does
140140
auto sleep = std::max(max + c, min);

0 commit comments

Comments
 (0)