1function [estVal] = estimator_ekf(self,
nodes)
3% Extended Kalman Filter resource demand estimator
4% This demand estimator
is based on the method proposed in:
6% Kumar, Dinesh and Tantawi, Asser and Zhang, Li
7% Real-Time Performance Modeling
for Adaptive Software Systems 2009
9% Uses a LINE solver (
default: SolverAuto) to compute the predicted
10% measurement function h(x), replacing the M/GI/1-PS analytical formula.
11% The Jacobian
is computed numerically via finite differences.
13% Copyright (c) 2012-2026, Imperial College London
15% This code
is released under the 3-Clause BSD License.
17% This estimator at present can handle estimation on a single resource.
20% rescale utilization to be mean number of busy servers
21 sn = self.model.getStruct;
23 if isfinite(node.getNumberOfServers())
24 U = self.getAggrUtil(node);
26 avgU = U.data * node.getNumberOfServers();
30 % obtain per class metrics
32 avgArvR{r} = self.getArvR(node, self.model.classes{r});
33 if isempty(avgArvR{r})
34 error(
'Arrival rate data for node %d in class %d is missing.', self.model.getNodeIndex(node), r);
36 avgArvR{r} = avgArvR{r}.data;
38 avgRespT{r} = self.getRespT(node, self.model.classes{r});
39 if isempty(avgRespT{r})
40 error(
'Response time data for node %d in class %d is missing.', self.model.getNodeIndex(node), r);
42 avgRespT{r} = avgRespT{r}.data;
48 avgA = cell2mat(avgArvR);
49 avgR = cell2mat(avgRespT);
52 case 'MATLAB:catenate:dimensionMismatch'
53 error(
'Sampled metrics have different number of samples, use interpolate() before starting this estimation algorithm.');
57 % Get solver
class from options (default: @SolverAuto)
58 if isfield(self.options, 'solver') && ~isempty(self.options.solver)
59 solverType = self.options.solver;
61 solverType = @SolverAuto;
64 % Support warm-start from a previous estimate
65 if isfield(self.options, 'x0') && ~isempty(self.options.x0)
71 [estVal] = ekf_data(avgU, avgR, avgA, node.getNumberOfServers(), self.options.iter_max, self.model, node, solverType, x0);
75% ekf procedure based on the common data format
76function [demEst] = ekf_data(cpuUtil, rAvgTimes, avgArvR, numServers, ITERMAX, model, node, solverType, x0)
79 disp('NaN values found for CPU Utilization. Removing NaN values.');
80 cpuUtil = cpuUtil(a == 0);
81 rAvgTimes = rAvgTimes(a == 0,:);
82 avgArvR = avgArvR(a == 0,:);
85 a = sum(avgArvR,2) == 0;
87 disp('Removing sampling intervals with zero throughput for all request types.');
88 cpuUtil = cpuUtil(a == 0);
89 rAvgTimes = rAvgTimes(a == 0,:);
90 avgArvR = avgArvR(a == 0,:);
93 %% number of resources
96 R = size(rAvgTimes,2);
99 % x(r)
is the mean service demand of class r (visits are assumed unitary)
100 if nargin >= 9 && ~isempty(x0)
103 x = rand(R,1).*max(rAvgTimes); % randomize service demand in [0,max(avgRTime)] for each class
107 mCovNoise = diag(repmat(0.01, 1, R + 1));
108 pCovNoise = eye(size(x,1)).*0.001;
110 %% Initialize solver: get sn struct and solver options once
111 % Use sn_set_service for fast parameter updates (no model rebuild)
112 sn = model.getStruct();
113 stIdx = node.stationIndex;
115 % Determine solver analyzer function from solver type
116 solver = solverType(model);
117 solverOpts = solver.getOptions();
118 solverAnalyzer = getSolverAnalyzer(solver, solverOpts);
120 %% optimization program
121 N = size(cpuUtil,1); % number of experiments
123 a_min = zeros(size(x));
124 a_max = zeros(size(x)) + inf;
126 for n=1:min(N, ITERMAX)
131 % Pn = Fn Pn-1 F'n + Qn
135 stepUtil = cpuUtil(n);
136 stepResponse = rAvgTimes(n, :);
138 [z_n, sn] = getPredictedMeasurement(x_n, R, sn, stIdx, solverAnalyzer);
139 H_n = getJacobian(x_n, R, sn, stIdx, solverAnalyzer, z_n);
140 z = getMeasurement(R, stepUtil, stepResponse);
143 % Sn = Hn Pn H'n + Rn
145 S_n = H_n * P_n * H_nT + mCovNoise;
147 % Kalman Gain Kn = Pn H'n S^-1n
148 K_n = P_n * H_nT * inv(S_n);
152 xlower = (stepBound * a_min) + (1-stepBound)*x;
153 xupper = (stepBound * a_max) + (1-stepBound)*x;
154 x = min(xupper, max(xlower, x));
158 % Pnn = (I - Kn Hn) Pn
159 p = (eye(size(x)) - (K_n * H_n)) * P_n;
165function solverAnalyzer = getSolverAnalyzer(solver, solverOpts)
166% Return a function handle that takes sn and returns [Q,U,R,T].
167% Selects the appropriate low-level analyzer based on solver type,
168% calling the solver's analyzer function directly to avoid OOP overhead.
169 if isa(solver, 'SolverMVA')
170 solverAnalyzer = @(sn_arg) solver_mva_analyzer(sn_arg, solverOpts);
171 elseif isa(solver, 'SolverAUTO') || isa(solver, 'SolverAuto')
172 mvaOpts = SolverMVA.defaultOptions;
173 solverAnalyzer = @(sn_arg) solver_mva_analyzer(sn_arg, mvaOpts);
174 elseif isa(solver, 'SolverNC')
175 solverAnalyzer = @(sn_arg) solver_nc_analyzer(sn_arg, solverOpts);
176 elseif isa(solver, 'SolverCTMC')
177 solverAnalyzer = @(sn_arg) solver_ctmc_analyzer(sn_arg, solverOpts);
178 elseif isa(solver, 'SolverFluid')
179 solverAnalyzer = @(sn_arg) solver_fluid_analyzer(sn_arg, solverOpts);
180 elseif isa(solver, 'SolverMAM')
181 solverAnalyzer = @(sn_arg) solver_mam_analyzer(sn_arg, solverOpts);
183 % Fallback: wrap via the solver object
184 solverAnalyzer = @(sn_arg) solverAnalyzerWrapper(solver, sn_arg);
188function [Q,U,R,T] = solverAnalyzerWrapper(solver, sn)
189% Generic wrapper: update the model's cached sn and re-solve.
190 solver.model.sn = sn;
192 Q = solver.getAvgQLen();
193 U = solver.getAvgUtil();
194 R = solver.getAvgRespT();
195 T = solver.getAvgTput();
198function [Hx] = getJacobian(x, R, sn, stIdx, solverAnalyzer, h0)
199% Compute Jacobian numerically via finite differences.
204 x_pert(c) = x_pert(c) + delta;
205 % Update sn for perturbation
208 sn_pert = sn_set_service_coc(sn_pert, stIdx, cc, 1/x_pert(cc));
210 [~,U_pert,R_pert] = solverAnalyzer(sn_pert);
211 h_pert = zeros(R+1, 1);
213 h_pert(cc) = R_pert(stIdx, cc);
215 h_pert(R+1) = sum(U_pert(stIdx, :));
216 Hx(:, c) = (h_pert - h0) / delta;
220function [h] = getMeasurement(R, util, responseTimes)
223 h(c) = responseTimes(c);
228function [h, sn] = getPredictedMeasurement(x, R, sn, stIdx, solverAnalyzer)
229% Compute predicted measurement using a LINE solver analyzer.
230% Updates service rates in the sn struct via sn_set_service and solves.
232 sn = sn_set_service_coc(sn, stIdx, c, 1/x(c));
235 [~, U, RN] = solverAnalyzer(sn);
241 h(R+1) = sum(U(stIdx, :));