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 <format>
21 #include <iostream>
22 
23 namespace phosphor::fan::control::json
24 {
25 
26 std::map<size_t, uint64_t> TargetFromGroupMax::_speedFromGroupsMap;
27 size_t TargetFromGroupMax::_groupIndexCounter = 0;
28 
29 using json = nlohmann::json;
30 using namespace phosphor::logging;
31 
TargetFromGroupMax(const json & jsonObj,const std::vector<Group> & groups)32 TargetFromGroupMax::TargetFromGroupMax(const json& jsonObj,
33                                        const std::vector<Group>& groups) :
34     ActionBase(jsonObj, groups)
35 {
36     setHysteresis(jsonObj);
37     setMap(jsonObj);
38     setIndex();
39 }
40 
run(Zone & zone)41 void TargetFromGroupMax::run(Zone& zone)
42 {
43     // Holds the max property value of groups
44     auto maxGroup = processGroups();
45 
46     // Group with non-numeric property value will be skipped from processing
47     if (maxGroup)
48     {
49         /*The maximum property value from the group*/
50         uint64_t groupValue =
51             static_cast<uint64_t>(std::get<double>(maxGroup.value()));
52 
53         // Only check if previous and new values differ
54         if (groupValue != _prevGroupValue)
55         {
56             // Value is decreasing and the change is greater than the
57             // negative hysteresis; or value is increasing and the change
58             // is greater than the positive hysteresis
59             if (((groupValue < _prevGroupValue) &&
60                  (_prevGroupValue - groupValue > _negHysteresis)) ||
61                 ((groupValue > _prevGroupValue) &&
62                  (groupValue - _prevGroupValue > _posHysteresis)))
63             {
64                 /*The speed derived from mapping*/
65                 uint64_t groupSpeed = _speedFromGroupsMap[_groupIndex];
66 
67                 // Looping through the mapping table
68                 for (auto it = _valueToSpeedMap.begin();
69                      it != _valueToSpeedMap.end(); ++it)
70                 {
71                     // Value is at/below the first map key, or at/above the
72                     // last map key, set speed to the first or the last map
73                     // key's value respectively
74                     if (((it == _valueToSpeedMap.begin()) &&
75                          (groupValue <= it->first)) ||
76                         ((std::next(it, 1) == _valueToSpeedMap.end()) &&
77                          (groupValue >= it->first)))
78                     {
79                         groupSpeed = it->second;
80                         break;
81                     }
82                     // Value transitioned across a map key, update the speed
83                     // to this map key's value when the new group value is at
84                     // or above the map's key and below the next key
85                     if (groupValue >= it->first &&
86                         groupValue < std::next(it, 1)->first)
87                     {
88                         groupSpeed = it->second;
89                         break;
90                     }
91                 }
92                 _prevGroupValue = groupValue;
93                 _speedFromGroupsMap[_groupIndex] = groupSpeed;
94             }
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) { return x.second < y.second; });
101 
102         zone.setTarget(maxSpeedFromGroupsIter->second);
103     }
104     else
105     {
106         // std::cerr << "Failed to process groups for " << ActionBase::getName()
107         //           << ": Further processing will be skipped \n";
108     }
109 }
110 
setHysteresis(const json & jsonObj)111 void TargetFromGroupMax::setHysteresis(const json& jsonObj)
112 {
113     if (!jsonObj.contains("neg_hysteresis") ||
114         !jsonObj.contains("pos_hysteresis"))
115     {
116         throw ActionParseError{
117             ActionBase::getName(),
118             "Missing required neg_hysteresis or pos_hysteresis value"};
119     }
120     _negHysteresis = jsonObj["neg_hysteresis"].get<uint64_t>();
121     _posHysteresis = jsonObj["pos_hysteresis"].get<uint64_t>();
122 }
123 
setIndex()124 void TargetFromGroupMax::setIndex()
125 {
126     _groupIndex = _groupIndexCounter;
127     // Initialize the map of each group and their max values
128     _speedFromGroupsMap[_groupIndex] = 0;
129 
130     // Increase the index counter by one to specify the next group key
131     _groupIndexCounter += 1;
132 }
133 
setMap(const json & jsonObj)134 void TargetFromGroupMax::setMap(const json& jsonObj)
135 {
136     if (jsonObj.contains("map"))
137     {
138         for (const auto& map : jsonObj.at("map"))
139         {
140             if (!map.contains("value") || !map.contains("target"))
141             {
142                 throw ActionParseError{ActionBase::getName(),
143                                        "Missing value or target in map"};
144             }
145             else
146             {
147                 uint64_t val = map["value"].get<uint64_t>();
148                 uint64_t target = map["target"].get<uint64_t>();
149                 _valueToSpeedMap.insert(
150                     std::pair<uint64_t, uint64_t>(val, target));
151             }
152         }
153     }
154 
155     else
156     {
157         throw ActionParseError{ActionBase::getName(), "Missing required map"};
158     }
159 }
160 
processGroups()161 std::optional<PropertyVariantType> TargetFromGroupMax::processGroups()
162 {
163     // Holds the max property value of groups
164     std::optional<PropertyVariantType> max;
165 
166     for (const auto& group : _groups)
167     {
168         const auto& members = group.getMembers();
169         for (const auto& member : members)
170         {
171             PropertyVariantType value;
172             bool invalid = false;
173             try
174             {
175                 value = Manager::getObjValueVariant(
176                     member, group.getInterface(), group.getProperty());
177             }
178             catch (const std::out_of_range&)
179             {
180                 continue;
181             }
182 
183             // Only allow a group members to be
184             // numeric. Unlike with std::is_arithmetic, bools are not
185             // considered numeric here.
186             std::visit(
187                 [&group, &invalid, this](auto&& val) {
188                     using V = std::decay_t<decltype(val)>;
189                     if constexpr (!std::is_same_v<double, V> &&
190                                   !std::is_same_v<int32_t, V> &&
191                                   !std::is_same_v<int64_t, V>)
192                     {
193                         log<level::ERR>(
194                             std::format("{}: Group {}'s member "
195                                         "isn't numeric",
196                                         ActionBase::getName(), group.getName())
197                                 .c_str());
198                         invalid = true;
199                     }
200                 },
201                 value);
202             if (invalid)
203             {
204                 break;
205             }
206 
207             if (max && (value > max))
208             {
209                 max = value;
210             }
211             else if (!max)
212             {
213                 max = value;
214             }
215         }
216     }
217     return max;
218 }
219 
220 } // namespace phosphor::fan::control::json
221