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
109             {
110                 if (groupValue - _prevGroupValue > _negHysteresis)
111                 {
112                     for (auto it = _valueToSpeedMap.begin();
113                          it != _valueToSpeedMap.end(); ++it)
114                     {
115                         // Value is at/below the first map key, set speed to the
116                         // first map key's value
117                         if (it == _valueToSpeedMap.begin() &&
118                             groupValue <= it->first)
119                         {
120                             groupSpeed = it->second;
121                             break;
122                         }
123                         // Value is at/above last map key, set speed to the last
124                         // map key's value
125                         else if (std::next(it, 1) == _valueToSpeedMap.end() &&
126                                  groupValue >= it->first)
127                         {
128                             groupSpeed = it->second;
129                             break;
130                         }
131                         // Value increased & transitioned across a map key,
132                         // update speed to the next map key's value when new
133                         // value is above map's key and the key is at/above the
134                         // previous value
135                         if (groupValue > it->first &&
136                             it->first >= _prevGroupValue)
137                         {
138                             groupSpeed = std::next(it, 1)->second;
139                         }
140                         // Value increased & transitioned across a map key,
141                         // update speed to the map key's value when new value is
142                         // at the map's key and the key is above the previous
143                         // value
144                         else if (groupValue == it->first &&
145                                  it->first > _prevGroupValue)
146                         {
147                             groupSpeed = it->second;
148                         }
149                     }
150                 }
151                 _prevGroupValue = groupValue;
152                 _speedFromGroupsMap[_groupIndex] = groupSpeed;
153 
154                 // Get the maximum speed derived from all groups, and set target
155                 // for the Zone
156                 auto maxSpeedFromGroupsIter = std::max_element(
157                     _speedFromGroupsMap.begin(), _speedFromGroupsMap.end(),
158                     [](const auto& x, const auto& y) {
159                         return x.second < y.second;
160                     });
161 
162                 zone.setTarget(maxSpeedFromGroupsIter->second);
163             }
164         }
165     }
166     else
167     {
168         std::cerr << "Failed to process groups for " << ActionBase::getName()
169                   << ": Further processing will be skipped \n";
170     }
171 }
172 
173 void TargetFromGroupMax::setHysteresis(const json& jsonObj)
174 {
175     if (!jsonObj.contains("neg_hysteresis") ||
176         !jsonObj.contains("pos_hysteresis"))
177     {
178         throw ActionParseError{
179             ActionBase::getName(),
180             "Missing required neg_hysteresis or pos_hysteresis value"};
181     }
182     _negHysteresis = jsonObj["neg_hysteresis"].get<uint64_t>();
183     _posHysteresis = jsonObj["pos_hysteresis"].get<uint64_t>();
184 }
185 
186 void TargetFromGroupMax::setIndex()
187 {
188     _groupIndex = _groupIndexCounter;
189     // Initialize the map of each group and their max values
190     _speedFromGroupsMap[_groupIndex] = 0;
191 
192     // Increase the index counter by one to specify the next group key
193     _groupIndexCounter += 1;
194 }
195 
196 void TargetFromGroupMax::setMap(const json& jsonObj)
197 {
198     if (jsonObj.contains("map"))
199     {
200         for (const auto& map : jsonObj.at("map"))
201         {
202 
203             if (!map.contains("value") || !map.contains("target"))
204             {
205                 throw ActionParseError{ActionBase::getName(),
206                                        "Missing value or target in map"};
207             }
208             else
209             {
210                 uint64_t val = map["value"].get<uint64_t>();
211                 uint64_t target = map["target"].get<uint64_t>();
212                 _valueToSpeedMap.insert(
213                     std::pair<uint64_t, uint64_t>(val, target));
214             }
215         }
216     }
217 
218     else
219     {
220         throw ActionParseError{ActionBase::getName(), "Missing required map"};
221     }
222 }
223 
224 std::optional<PropertyVariantType> TargetFromGroupMax::processGroups()
225 {
226     // Holds the max property value of groups
227     std::optional<PropertyVariantType> max;
228 
229     for (const auto& group : _groups)
230     {
231         const auto& members = group.getMembers();
232         for (const auto& member : members)
233         {
234             PropertyVariantType value;
235             bool invalid = false;
236             try
237             {
238                 value = Manager::getObjValueVariant(
239                     member, group.getInterface(), group.getProperty());
240             }
241             catch (const std::out_of_range&)
242             {
243                 continue;
244             }
245 
246             // Only allow a group members to be
247             // numeric. Unlike with std::is_arithmetic, bools are not
248             // considered numeric here.
249             std::visit(
250                 [&group, &invalid, this](auto&& val) {
251                     using V = std::decay_t<decltype(val)>;
252                     if constexpr (!std::is_same_v<double, V> &&
253                                   !std::is_same_v<int32_t, V> &&
254                                   !std::is_same_v<int64_t, V>)
255                     {
256                         log<level::ERR>(fmt::format("{}: Group {}'s member "
257                                                     "isn't numeric",
258                                                     ActionBase::getName(),
259                                                     group.getName())
260                                             .c_str());
261                         invalid = true;
262                     }
263                 },
264                 value);
265             if (invalid)
266             {
267                 break;
268             }
269 
270             if (max && (value > max))
271             {
272                 max = value;
273             }
274             else if (!max)
275             {
276                 max = value;
277             }
278         }
279     }
280     return max;
281 }
282 
283 } // namespace phosphor::fan::control::json
284