1 /**
2  * Copyright © 2022 Ampere Computing
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include "target_from_group_max.hpp"
17 
18 #include "../manager.hpp"
19 
20 #include <fmt/format.h>
21 
22 #include <iostream>
23 
24 namespace phosphor::fan::control::json
25 {
26 
27 std::map<size_t, uint64_t> TargetFromGroupMax::_speedFromGroupsMap;
28 size_t TargetFromGroupMax::_groupIndexCounter = 0;
29 
30 using json = nlohmann::json;
31 using namespace phosphor::logging;
32 
33 TargetFromGroupMax::TargetFromGroupMax(const json& jsonObj,
34                                        const std::vector<Group>& groups) :
35     ActionBase(jsonObj, groups)
36 {
37     setHysteresis(jsonObj);
38     setMap(jsonObj);
39     setIndex();
40 }
41 
42 void TargetFromGroupMax::run(Zone& zone)
43 {
44     // Holds the max property value of groups
45     auto maxGroup = processGroups();
46 
47     // Group with non-numeric property value will be skipped from processing
48     if (maxGroup)
49     {
50         /*The maximum property value from the group*/
51         uint64_t groupValue =
52             static_cast<uint64_t>(std::get<double>(maxGroup.value()));
53 
54         // Only check if previous and new values differ
55         if (groupValue != _prevGroupValue)
56         {
57             /*The speed derived from mapping*/
58             uint64_t groupSpeed = _speedFromGroupsMap[_groupIndex];
59 
60             // Value is decreasing from previous  && greater than positive
61             // hysteresis
62             if ((groupValue < _prevGroupValue) &&
63                 (_prevGroupValue - groupValue > _posHysteresis))
64             {
65                 for (auto it = _valueToSpeedMap.rbegin();
66                      it != _valueToSpeedMap.rend(); ++it)
67                 {
68                     // Value is at/above last map key, set speed to the last map
69                     // key's value
70                     if (it == _valueToSpeedMap.rbegin() &&
71                         groupValue >= it->first)
72                     {
73                         groupSpeed = it->second;
74                         break;
75                     }
76                     // Value is at/below first map key, set speed to the first
77                     // map key's value
78                     else if (std::next(it, 1) == _valueToSpeedMap.rend() &&
79                              groupValue <= it->first)
80                     {
81                         groupSpeed = it->second;
82                         break;
83                     }
84                     // Value decreased & transitioned across a map key, update
85                     // speed to this map key's value when new value is at or
86                     // below map's key and the key is at/below the previous
87                     // value
88                     if (groupValue <= it->first && it->first <= _prevGroupValue)
89                     {
90                         groupSpeed = it->second;
91                     }
92                 }
93                 _prevGroupValue = groupValue;
94                 _speedFromGroupsMap[_groupIndex] = groupSpeed;
95 
96                 // Get the maximum speed derived from all groups, and set target
97                 // for the Zone
98                 auto maxSpeedFromGroupsIter = std::max_element(
99                     _speedFromGroupsMap.begin(), _speedFromGroupsMap.end(),
100                     [](const auto& x, const auto& y) {
101                         return x.second < y.second;
102                     });
103 
104                 zone.setTarget(maxSpeedFromGroupsIter->second);
105             }
106             // Value is increasing from previous && greater than negative
107             // hysteresis
108             else if ((groupValue > _prevGroupValue) &&
109                      (groupValue - _prevGroupValue > _negHysteresis))
110             {
111                 for (auto it = _valueToSpeedMap.begin();
112                      it != _valueToSpeedMap.end(); ++it)
113                 {
114                     // Value is at/below the first map key, set speed to the
115                     // first map key's value
116                     if (it == _valueToSpeedMap.begin() &&
117                         groupValue <= it->first)
118                     {
119                         groupSpeed = it->second;
120                         break;
121                     }
122                     // Value is at/above last map key, set speed to the last
123                     // map key's value
124                     else if (std::next(it, 1) == _valueToSpeedMap.end() &&
125                              groupValue >= it->first)
126                     {
127                         groupSpeed = it->second;
128                         break;
129                     }
130                     // Value increased & transitioned across a map key,
131                     // update speed to the next map key's value when new
132                     // value is above map's key and the key is at/above the
133                     // previous value
134                     if (groupValue > it->first && it->first >= _prevGroupValue)
135                     {
136                         groupSpeed = std::next(it, 1)->second;
137                     }
138                     // Value increased & transitioned across a map key,
139                     // update speed to the map key's value when new value is
140                     // at the map's key and the key is above the previous
141                     // value
142                     else if (groupValue == it->first &&
143                              it->first > _prevGroupValue)
144                     {
145                         groupSpeed = it->second;
146                     }
147                 }
148                 _prevGroupValue = groupValue;
149                 _speedFromGroupsMap[_groupIndex] = groupSpeed;
150 
151                 // Get the maximum speed derived from all groups, and set target
152                 // for the Zone
153                 auto maxSpeedFromGroupsIter = std::max_element(
154                     _speedFromGroupsMap.begin(), _speedFromGroupsMap.end(),
155                     [](const auto& x, const auto& y) {
156                         return x.second < y.second;
157                     });
158 
159                 zone.setTarget(maxSpeedFromGroupsIter->second);
160             }
161         }
162     }
163     else
164     {
165         // std::cerr << "Failed to process groups for " << ActionBase::getName()
166         //           << ": Further processing will be skipped \n";
167     }
168 }
169 
170 void TargetFromGroupMax::setHysteresis(const json& jsonObj)
171 {
172     if (!jsonObj.contains("neg_hysteresis") ||
173         !jsonObj.contains("pos_hysteresis"))
174     {
175         throw ActionParseError{
176             ActionBase::getName(),
177             "Missing required neg_hysteresis or pos_hysteresis value"};
178     }
179     _negHysteresis = jsonObj["neg_hysteresis"].get<uint64_t>();
180     _posHysteresis = jsonObj["pos_hysteresis"].get<uint64_t>();
181 }
182 
183 void TargetFromGroupMax::setIndex()
184 {
185     _groupIndex = _groupIndexCounter;
186     // Initialize the map of each group and their max values
187     _speedFromGroupsMap[_groupIndex] = 0;
188 
189     // Increase the index counter by one to specify the next group key
190     _groupIndexCounter += 1;
191 }
192 
193 void TargetFromGroupMax::setMap(const json& jsonObj)
194 {
195     if (jsonObj.contains("map"))
196     {
197         for (const auto& map : jsonObj.at("map"))
198         {
199 
200             if (!map.contains("value") || !map.contains("target"))
201             {
202                 throw ActionParseError{ActionBase::getName(),
203                                        "Missing value or target in map"};
204             }
205             else
206             {
207                 uint64_t val = map["value"].get<uint64_t>();
208                 uint64_t target = map["target"].get<uint64_t>();
209                 _valueToSpeedMap.insert(
210                     std::pair<uint64_t, uint64_t>(val, target));
211             }
212         }
213     }
214 
215     else
216     {
217         throw ActionParseError{ActionBase::getName(), "Missing required map"};
218     }
219 }
220 
221 std::optional<PropertyVariantType> TargetFromGroupMax::processGroups()
222 {
223     // Holds the max property value of groups
224     std::optional<PropertyVariantType> max;
225 
226     for (const auto& group : _groups)
227     {
228         const auto& members = group.getMembers();
229         for (const auto& member : members)
230         {
231             PropertyVariantType value;
232             bool invalid = false;
233             try
234             {
235                 value = Manager::getObjValueVariant(
236                     member, group.getInterface(), group.getProperty());
237             }
238             catch (const std::out_of_range&)
239             {
240                 continue;
241             }
242 
243             // Only allow a group members to be
244             // numeric. Unlike with std::is_arithmetic, bools are not
245             // considered numeric here.
246             std::visit(
247                 [&group, &invalid, this](auto&& val) {
248                     using V = std::decay_t<decltype(val)>;
249                     if constexpr (!std::is_same_v<double, V> &&
250                                   !std::is_same_v<int32_t, V> &&
251                                   !std::is_same_v<int64_t, V>)
252                     {
253                         log<level::ERR>(fmt::format("{}: Group {}'s member "
254                                                     "isn't numeric",
255                                                     ActionBase::getName(),
256                                                     group.getName())
257                                             .c_str());
258                         invalid = true;
259                     }
260                 },
261                 value);
262             if (invalid)
263             {
264                 break;
265             }
266 
267             if (max && (value > max))
268             {
269                 max = value;
270             }
271             else if (!max)
272             {
273                 max = value;
274             }
275         }
276     }
277     return max;
278 }
279 
280 } // namespace phosphor::fan::control::json
281