anawu.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. /*##############################################################################
  2. HPCC SYSTEMS software Copyright (C) 2019 HPCC Systems®.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. ############################################################################## */
  13. #include "jliball.hpp"
  14. #include "workunit.hpp"
  15. #include "anacommon.hpp"
  16. #include "anarule.hpp"
  17. #include "anawu.hpp"
  18. class WuScope;
  19. class WorkunitAnalyser;
  20. class WuScopeHashTable : public SuperHashTableOf<WuScope, const char>
  21. {
  22. public:
  23. ~WuScopeHashTable() { _releaseAll(); }
  24. void addNew(WuScope * newWuScope) { SuperHashTableOf::addNew(newWuScope);}
  25. virtual void onAdd(void *et) {};
  26. virtual void onRemove(void *et);
  27. virtual unsigned getHashFromElement(const void *et) const;
  28. virtual unsigned getHashFromFindParam(const void *fp) const;
  29. virtual const void * getFindParam(const void *et) const;
  30. virtual bool matchesFindParam(const void *et, const void *key, unsigned fphash) const;
  31. virtual bool matchesElement(const void *et, const void *searchET) const;
  32. };
  33. inline unsigned hashScope(const char * name) { return hashc((const byte *)name, strlen(name), 0); }
  34. class WuScope : public CInterface, implements IWuEdge, implements IWuActivity
  35. {
  36. public:
  37. WuScope(const char * _name, WuScope * _parent) : name(_name), parent(_parent)
  38. {
  39. attrs.setown(createPTree());
  40. }
  41. void applyRules(WorkunitAnalyser & analyser);
  42. void connectActivities();
  43. WuScope * select(const char * scope); // Returns matching wuScope (create if no pre-existing)
  44. void setInput(unsigned i, WuScope * scope); // Save i'th target in inputs
  45. void setOutput(unsigned i, WuScope * scope); // Save i'th source in output
  46. virtual stat_type getStatRaw(StatisticKind kind, StatisticKind variant = StKindNone) const;
  47. virtual unsigned getAttr(WuAttr kind) const;
  48. virtual void getAttr(StringBuffer & result, WuAttr kind) const;
  49. inline const char * queryName() const { return name; }
  50. virtual const char * getFullScopeName(StringBuffer & fullScopeName) const override
  51. {
  52. // Building full scope name dynamically is good enough as long as there's not too many calls here
  53. if (parent)
  54. {
  55. parent->getFullScopeName(fullScopeName);
  56. if (fullScopeName.length())
  57. return fullScopeName.append(":").append(name);
  58. else
  59. return fullScopeName.set(name);
  60. }
  61. else
  62. return fullScopeName.set(name);
  63. }
  64. inline IPropertyTree * queryAttrs() const { return attrs; }
  65. virtual WuScope * querySource() override;
  66. virtual WuScope * queryTarget() override;
  67. virtual IWuEdge * queryInput(unsigned idx) { return (idx < inputs.size()) ? inputs[idx]:nullptr; }
  68. virtual IWuEdge * queryOutput(unsigned idx) { return (idx < outputs.size()) ? outputs[idx]:nullptr; }
  69. StatisticScopeType queryScopeType() const { return (StatisticScopeType)attrs->getPropInt("@stype");}
  70. const char * queryScopeTypeName() const { return ::queryScopeTypeName(queryScopeType()); }
  71. void trace(unsigned indent=0) const;
  72. protected:
  73. StringAttr name;
  74. WuScope * parent = nullptr;
  75. Owned<IPropertyTree> attrs;
  76. WuScopeHashTable scopes;
  77. std::vector<WuScope *> inputs;
  78. std::vector<WuScope *> outputs;
  79. };
  80. //-----------------------------------------------------------------------------------------------------------
  81. typedef enum {wutOptValueTypeFirst=0, wutOptValueTypeMSec=0, wutOptValueTypeSeconds, wutOptValueTypePercent, wutOptValueTypeCount, wutOptValueTypeMax} WutOptValueType;
  82. struct WuOption
  83. {
  84. WutOptionType option;
  85. const char * name;
  86. stat_type defaultValue;
  87. WutOptValueType type;
  88. };
  89. constexpr struct WuOption wuOptionsDefaults[watOptMax]
  90. = { {watOptMinInterestingTime, "minInterestingTime", 1000, wutOptValueTypeMSec},
  91. {watOptMinInterestingCost, "minInterestingCost", 30000, wutOptValueTypeMSec},
  92. {watOptSkewThreshold, "skewThreshold", 20, wutOptValueTypePercent},
  93. {watOptMinRowsPerNode, "minRowsPerNode", 1000, wutOptValueTypeCount},
  94. {watPreFilteredKJThreshold, "preFilteredKJThreshold", 50, wutOptValueTypePercent} };
  95. constexpr bool checkWuOptionsDefaults(int i = watOptMax)
  96. {
  97. return ((wuOptionsDefaults[i-1].name != nullptr && (wuOptionsDefaults[i-1].option == i-1) && wuOptionsDefaults[i-1].type < wutOptValueTypeMax ) &&
  98. (i==1 || checkWuOptionsDefaults(i-1)));
  99. }
  100. static_assert(checkWuOptionsDefaults(), "wuOptionsDefaults[] not populated correctly");
  101. class WuAnalyserOptions : public IAnalyserOptions
  102. {
  103. public:
  104. WuAnalyserOptions()
  105. {
  106. for (int opt = watOptFirst; opt < watOptMax; opt++)
  107. setOptionValue(static_cast<WutOptionType>(opt), wuOptionsDefaults[opt].defaultValue);
  108. }
  109. void setOptionValue(WutOptionType opt, __int64 val)
  110. {
  111. assertex(opt<watOptMax);
  112. switch(wuOptionsDefaults[opt].type)
  113. {
  114. case wutOptValueTypeMSec:
  115. wuOptions[opt] = msecs2StatUnits(val);
  116. break;
  117. case wutOptValueTypeSeconds:
  118. wuOptions[opt] = seconds2StatUnits(val);
  119. break;
  120. case wutOptValueTypePercent:
  121. wuOptions[opt] = statPercent((stat_type)val);
  122. break;
  123. case wutOptValueTypeCount:
  124. wuOptions[opt] = (stat_type) val;
  125. break;
  126. default:
  127. throw MakeStringException(-1, "WuAnalyserOptions::setOptionValue - unknown wuOptionsDefaults[%d].type=%d", (int) opt, (int) wuOptionsDefaults[opt].type);
  128. }
  129. }
  130. void applyConfig(IPropertyTree *options)
  131. {
  132. if (!options) return;
  133. for (int opt = watOptFirst; opt < watOptMax; opt++)
  134. {
  135. StringBuffer wuOptionName("@");
  136. wuOptionName.append(wuOptionsDefaults[opt].name);
  137. __int64 val = options->getPropInt64(wuOptionName, -1);
  138. if (val!=-1)
  139. setOptionValue(static_cast<WutOptionType>(opt), val);
  140. }
  141. }
  142. void applyConfig(IConstWorkUnit * wu)
  143. {
  144. for (int opt = watOptFirst; opt < watOptMax; opt++)
  145. {
  146. StringBuffer wuOptionName("analyzer_");
  147. wuOptionName.append(wuOptionsDefaults[opt].name);
  148. __int64 val = wu->getDebugValueInt64(wuOptionName, -1);
  149. if (val!=-1)
  150. setOptionValue(static_cast<WutOptionType>(opt), val);
  151. }
  152. }
  153. stat_type queryOption(WutOptionType opt) const override { return wuOptions[opt]; }
  154. private:
  155. stat_type wuOptions[watOptMax];
  156. };
  157. //-----------------------------------------------------------------------------------------------------------
  158. class WorkunitAnalyser
  159. {
  160. public:
  161. WorkunitAnalyser();
  162. void check(const char * scope, IWuActivity & activity);
  163. void analyse(IConstWorkUnit * wu);
  164. void print();
  165. void update(IWorkUnit *wu);
  166. void applyConfig(IPropertyTree *cfg);
  167. protected:
  168. void collateWorkunitStats(IConstWorkUnit * workunit, const WuScopeFilter & filter);
  169. WuScope * selectFullScope(const char * scope);
  170. CIArrayOf<AActivityRule> rules;
  171. CIArrayOf<PerformanceIssue> issues;
  172. WuScope root;
  173. WuAnalyserOptions options;
  174. };
  175. //-----------------------------------------------------------------------------------------------------------
  176. void WuScopeHashTable::onRemove(void *et)
  177. {
  178. WuScope * elem = reinterpret_cast<WuScope *>(et);
  179. elem->Release();
  180. }
  181. unsigned WuScopeHashTable::getHashFromElement(const void *et) const
  182. {
  183. const WuScope * elem = reinterpret_cast<const WuScope *>(et);
  184. return hashScope(elem->queryName());
  185. }
  186. unsigned WuScopeHashTable::getHashFromFindParam(const void *fp) const
  187. {
  188. const char * search = reinterpret_cast<const char *>(fp);
  189. return hashScope(search);
  190. }
  191. const void * WuScopeHashTable::getFindParam(const void *et) const
  192. {
  193. const WuScope * elem = reinterpret_cast<const WuScope *>(et);
  194. return elem->queryName();
  195. }
  196. bool WuScopeHashTable::matchesFindParam(const void *et, const void *key, unsigned fphash) const
  197. {
  198. const WuScope * elem = reinterpret_cast<const WuScope *>(et);
  199. const char * search = reinterpret_cast<const char *>(key);
  200. return streq(elem->queryName(), search);
  201. }
  202. bool WuScopeHashTable::matchesElement(const void *et, const void *searchET) const
  203. {
  204. const WuScope * elem = reinterpret_cast<const WuScope *>(et);
  205. const WuScope * searchElem = reinterpret_cast<const WuScope *>(searchET);
  206. return streq(elem->queryName(), searchElem->queryName());
  207. }
  208. //-----------------------------------------------------------------------------------------------------------
  209. void WuScope::applyRules(WorkunitAnalyser & analyser)
  210. {
  211. for (auto & cur : scopes)
  212. {
  213. if (cur.queryScopeType() == SSTactivity)
  214. analyser.check(cur.queryName(), cur);
  215. cur.applyRules(analyser);
  216. }
  217. }
  218. void WuScope::connectActivities()
  219. {
  220. //Yuk - scopes can be added to while they are being iterated, so need to create a list first
  221. CICopyArrayOf<WuScope> toWalk;
  222. for (auto & cur : scopes)
  223. toWalk.append(cur);
  224. ForEachItemIn(i, toWalk)
  225. {
  226. WuScope & cur = toWalk.item(i);
  227. if (cur.queryScopeType() == SSTedge)
  228. {
  229. WuScope * source = cur.querySource();
  230. WuScope * sink = cur.queryTarget();
  231. if (source)
  232. source->setOutput(cur.getAttr(WaSourceIndex), &cur);
  233. if (sink)
  234. sink->setInput(cur.getAttr(WaTargetIndex), &cur);
  235. }
  236. cur.connectActivities();
  237. }
  238. }
  239. WuScope * WuScope::select(const char * scope)
  240. {
  241. assertex(scope);
  242. WuScope * match = scopes.find(scope);
  243. if (match)
  244. return match;
  245. match = new WuScope(scope, this);
  246. scopes.addNew(match);
  247. return match;
  248. }
  249. void WuScope::setInput(unsigned i, WuScope * scope)
  250. {
  251. while (inputs.size() <= i)
  252. inputs.push_back(nullptr);
  253. inputs[i] = scope;
  254. }
  255. void WuScope::setOutput(unsigned i, WuScope * scope)
  256. {
  257. while (outputs.size() <= i)
  258. outputs.push_back(nullptr);
  259. outputs[i] = scope;
  260. }
  261. WuScope * WuScope::querySource()
  262. {
  263. const char * source = attrs->queryProp("@IdSource");
  264. if (!source)
  265. return nullptr;
  266. return parent->select(source);
  267. }
  268. WuScope * WuScope::queryTarget()
  269. {
  270. const char * target = attrs->queryProp("@IdTarget");
  271. if (!target)
  272. return nullptr;
  273. return parent->select(target);
  274. }
  275. stat_type WuScope::getStatRaw(StatisticKind kind, StatisticKind variant) const
  276. {
  277. StringBuffer name;
  278. name.append('@').append(queryStatisticName(kind | variant));
  279. return attrs->getPropInt64(name);
  280. }
  281. unsigned WuScope::getAttr(WuAttr attr) const
  282. {
  283. StringBuffer name;
  284. name.append('@').append(queryWuAttributeName(attr));
  285. return attrs->getPropInt64(name);
  286. }
  287. void WuScope::getAttr(StringBuffer & result, WuAttr attr) const
  288. {
  289. StringBuffer name;
  290. name.append('@').append(queryWuAttributeName(attr));
  291. attrs->getProp(name, result);
  292. }
  293. void WuScope::trace(unsigned indent) const
  294. {
  295. printf("%*s%s: \"%s\" (", indent, " ", queryScopeTypeName(), queryName());
  296. for (auto in : inputs)
  297. printf("%s ", in->queryName());
  298. printf("->");
  299. for (auto out : outputs)
  300. printf(" %s", out->queryName());
  301. printf(") [");
  302. printf("children: ");
  303. for(auto & scope: scopes)
  304. {
  305. printf("%s ", scope.queryName());
  306. }
  307. printf("] {\n");
  308. printXML(queryAttrs(), indent);
  309. for (auto & cur : scopes)
  310. {
  311. cur.trace(indent+4);
  312. }
  313. printf("%*s}\n",indent, " ");
  314. }
  315. //-----------------------------------------------------------------------------------------------------------
  316. /* Callback used to output scope properties as xml */
  317. class StatsGatherer : public IWuScopeVisitor
  318. {
  319. public:
  320. StatsGatherer(IPropertyTree * _scope) : scope(_scope){}
  321. virtual void noteStatistic(StatisticKind kind, unsigned __int64 value, IConstWUStatistic & cur) override
  322. {
  323. StringBuffer name;
  324. name.append('@').append(queryStatisticName(kind));
  325. scope->setPropInt64(name, value);
  326. }
  327. virtual void noteAttribute(WuAttr attr, const char * value)
  328. {
  329. StringBuffer name;
  330. name.append('@').append(queryWuAttributeName(attr));
  331. scope->setProp(name, value);
  332. }
  333. virtual void noteHint(const char * kind, const char * value)
  334. {
  335. throwUnexpected();
  336. }
  337. virtual void noteException(IConstWUException & exception)
  338. {
  339. throwUnexpected();
  340. }
  341. IPropertyTree * scope;
  342. };
  343. //-----------------------------------------------------------------------------------------------------------
  344. WorkunitAnalyser::WorkunitAnalyser() : root("", nullptr)
  345. {
  346. gatherRules(rules);
  347. }
  348. void WorkunitAnalyser::applyConfig(IPropertyTree *cfg)
  349. {
  350. options.applyConfig(cfg);
  351. }
  352. void WorkunitAnalyser::check(const char * scope, IWuActivity & activity)
  353. {
  354. if (activity.getStatRaw(StTimeLocalExecute, StMaxX) < options.queryOption(watOptMinInterestingTime))
  355. return;
  356. Owned<PerformanceIssue> highestCostIssue;
  357. ForEachItemIn(i, rules)
  358. {
  359. if (rules.item(i).isCandidate(activity))
  360. {
  361. Owned<PerformanceIssue> issue (new PerformanceIssue);
  362. if (rules.item(i).check(*issue, activity, options))
  363. {
  364. if (issue->getCost() >= options.queryOption(watOptMinInterestingCost))
  365. {
  366. if (!highestCostIssue || highestCostIssue->getCost() < issue->getCost())
  367. highestCostIssue.setown(issue.getClear());
  368. }
  369. }
  370. }
  371. }
  372. if (highestCostIssue)
  373. {
  374. StringBuffer fullScopeName;
  375. activity.getFullScopeName(fullScopeName);
  376. highestCostIssue->setScope(fullScopeName);
  377. issues.append(*highestCostIssue.getClear());
  378. }
  379. }
  380. void WorkunitAnalyser::analyse(IConstWorkUnit * wu)
  381. {
  382. options.applyConfig(wu);
  383. WuScopeFilter filter;
  384. filter.addOutputProperties(PTstatistics).addOutputProperties(PTattributes);
  385. filter.finishedFilter();
  386. collateWorkunitStats(wu, filter);
  387. root.connectActivities();
  388. // root.trace();
  389. root.applyRules(*this);
  390. issues.sort(compareIssuesCostOrder);
  391. }
  392. void WorkunitAnalyser::print()
  393. {
  394. ForEachItemIn(i, issues)
  395. issues.item(i).print();
  396. }
  397. void WorkunitAnalyser::update(IWorkUnit *wu)
  398. {
  399. ForEachItemIn(i, issues)
  400. issues.item(i).createException(wu);
  401. }
  402. void WorkunitAnalyser::collateWorkunitStats(IConstWorkUnit * workunit, const WuScopeFilter & filter)
  403. {
  404. Owned<IConstWUScopeIterator> iter = &workunit->getScopeIterator(filter);
  405. ForEach(*iter)
  406. {
  407. try
  408. {
  409. WuScope * scope = selectFullScope(iter->queryScope());
  410. StatsGatherer callback(scope->queryAttrs());
  411. scope->queryAttrs()->setPropInt("@stype", iter->getScopeType());
  412. iter->playProperties(callback);
  413. }
  414. catch (IException * e)
  415. {
  416. e->Release();
  417. }
  418. }
  419. }
  420. WuScope * WorkunitAnalyser::selectFullScope(const char * scope)
  421. {
  422. StringBuffer temp;
  423. WuScope * resolved = &root;
  424. for (;;)
  425. {
  426. if (!*scope)
  427. return resolved;
  428. const char * dot = strchr(scope, ':');
  429. if (!dot)
  430. return resolved->select(scope);
  431. temp.clear().append(dot-scope, scope);
  432. resolved = resolved->select(temp.str());
  433. scope = dot+1;
  434. }
  435. }
  436. //---------------------------------------------------------------------------------------------------------------------
  437. void WUANALYSIS_API analyseWorkunit(IWorkUnit * wu, IPropertyTree *options)
  438. {
  439. WorkunitAnalyser analyser;
  440. analyser.applyConfig(options);
  441. analyser.analyse(wu);
  442. analyser.update(wu);
  443. }
  444. void WUANALYSIS_API analyseAndPrintIssues(IConstWorkUnit * wu, bool updatewu)
  445. {
  446. WorkunitAnalyser analyser;
  447. analyser.analyse(wu);
  448. analyser.print();
  449. if (updatewu)
  450. {
  451. Owned<IWorkUnit> lockedwu = &(wu->lock());
  452. lockedwu->clearExceptions("Workunit Analyzer");
  453. analyser.update(lockedwu);
  454. }
  455. }