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