LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
estimator_ekf.m
1function [estVal] = estimator_ekf(self, nodes)
2node = nodes{1};
3% Extended Kalman Filter resource demand estimator
4% This demand estimator is based on the method proposed in:
5%
6% Kumar, Dinesh and Tantawi, Asser and Zhang, Li
7% Real-Time Performance Modeling for Adaptive Software Systems 2009
8%
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.
12%
13% Copyright (c) 2012-2026, Imperial College London
14% All rights reserved.
15% This code is released under the 3-Clause BSD License.
16
17% This estimator at present can handle estimation on a single resource.
18
19%%
20% rescale utilization to be mean number of busy servers
21 sn = self.model.getStruct;
22
23 if isfinite(node.getNumberOfServers())
24 U = self.getAggrUtil(node);
25 if ~isempty(U)
26 avgU = U.data * node.getNumberOfServers();
27 end
28 end
29
30 % obtain per class metrics
31 for r=1:sn.nclasses
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);
35 else
36 avgArvR{r} = avgArvR{r}.data;
37 end
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);
41 else
42 avgRespT{r} = avgRespT{r}.data;
43 end
44
45 end
46
47 try
48 avgA = cell2mat(avgArvR);
49 avgR = cell2mat(avgRespT);
50 catch me
51 switch me.identifier
52 case 'MATLAB:catenate:dimensionMismatch'
53 error('Sampled metrics have different number of samples, use interpolate() before starting this estimation algorithm.');
54 end
55 end
56
57 % Get solver class from options (default: @SolverAuto)
58 if isfield(self.options, 'solver') && ~isempty(self.options.solver)
59 solverType = self.options.solver;
60 else
61 solverType = @SolverAuto;
62 end
63
64 % Support warm-start from a previous estimate
65 if isfield(self.options, 'x0') && ~isempty(self.options.x0)
66 x0 = self.options.x0;
67 else
68 x0 = [];
69 end
70
71 [estVal] = ekf_data(avgU, avgR, avgA, node.getNumberOfServers(), self.options.iter_max, self.model, node, solverType, x0);
72 estVal = estVal(:)';
73end
74
75% ekf procedure based on the common data format
76function [demEst] = ekf_data(cpuUtil, rAvgTimes, avgArvR, numServers, ITERMAX, model, node, solverType, x0)
77 a = isnan(cpuUtil);
78 if sum(a) > 0
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,:);
83 end
84
85 a = sum(avgArvR,2) == 0;
86 if sum(a) > 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,:);
91 end
92
93 %% number of resources
94 M = 1;
95 %% number of classes
96 R = size(rAvgTimes,2);
97
98 %% initial state
99 % x(r) is the mean service demand of class r (visits are assumed unitary)
100 if nargin >= 9 && ~isempty(x0)
101 x = x0(:);
102 else
103 x = rand(R,1).*max(rAvgTimes); % randomize service demand in [0,max(avgRTime)] for each class
104 end
105 p = diag(x.^2);
106
107 mCovNoise = diag(repmat(0.01, 1, R + 1));
108 pCovNoise = eye(size(x,1)).*0.001;
109
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;
114
115 % Determine solver analyzer function from solver type
116 solver = solverType(model);
117 solverOpts = solver.getOptions();
118 solverAnalyzer = getSolverAnalyzer(solver, solverOpts);
119
120 %% optimization program
121 N = size(cpuUtil,1); % number of experiments
122 stepBound = 0.6;
123 a_min = zeros(size(x));
124 a_max = zeros(size(x)) + inf;
125
126 for n=1:min(N, ITERMAX)
127 % predict
128 % xn = Fn xn-1
129 x_n = x;
130
131 % Pn = Fn Pn-1 F'n + Qn
132 P_n = p + pCovNoise;
133
134 % update
135 stepUtil = cpuUtil(n);
136 stepResponse = rAvgTimes(n, :);
137
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);
141 y_n = z - z_n;
142
143 % Sn = Hn Pn H'n + Rn
144 H_nT = H_n';
145 S_n = H_n * P_n * H_nT + mCovNoise;
146
147 % Kalman Gain Kn = Pn H'n S^-1n
148 K_n = P_n * H_nT * inv(S_n);
149
150 % xnn = xn + Kn y~n
151 x = x_n + K_n * y_n;
152 xlower = (stepBound * a_min) + (1-stepBound)*x;
153 xupper = (stepBound * a_max) + (1-stepBound)*x;
154 x = min(xupper, max(xlower, x));
155 if (sum(x) < 0)
156 x = x * -1;
157 end
158 % Pnn = (I - Kn Hn) Pn
159 p = (eye(size(x)) - (K_n * H_n)) * P_n;
160
161 end
162 [demEst] = x(1:M);
163end
164
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);
182 else
183 % Fallback: wrap via the solver object
184 solverAnalyzer = @(sn_arg) solverAnalyzerWrapper(solver, sn_arg);
185 end
186end
187
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;
191 solver.result = [];
192 Q = solver.getAvgQLen();
193 U = solver.getAvgUtil();
194 R = solver.getAvgRespT();
195 T = solver.getAvgTput();
196end
197
198function [Hx] = getJacobian(x, R, sn, stIdx, solverAnalyzer, h0)
199% Compute Jacobian numerically via finite differences.
200 delta = 1e-6;
201 Hx = zeros(R+1, R);
202 for c = 1:R
203 x_pert = x;
204 x_pert(c) = x_pert(c) + delta;
205 % Update sn for perturbation
206 sn_pert = sn;
207 for cc = 1:R
208 sn_pert = sn_set_service_coc(sn_pert, stIdx, cc, 1/x_pert(cc));
209 end
210 [~,U_pert,R_pert] = solverAnalyzer(sn_pert);
211 h_pert = zeros(R+1, 1);
212 for cc = 1:R
213 h_pert(cc) = R_pert(stIdx, cc);
214 end
215 h_pert(R+1) = sum(U_pert(stIdx, :));
216 Hx(:, c) = (h_pert - h0) / delta;
217 end
218end
219
220function [h] = getMeasurement(R, util, responseTimes)
221 h = zeros(R+1,1);
222 for c=1:R
223 h(c) = responseTimes(c);
224 end
225 h(R+1) = util;
226end
227
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.
231 for c = 1:R
232 sn = sn_set_service_coc(sn, stIdx, c, 1/x(c));
233 end
234
235 [~, U, RN] = solverAnalyzer(sn);
236
237 h = zeros(R+1, 1);
238 for c = 1:R
239 h(c) = RN(stIdx, c);
240 end
241 h(R+1) = sum(U(stIdx, :));
242end
Definition mmt.m:124