001 package org.hackystat.telemetry.analyzer.evaluator; 002 003 import org.hackystat.telemetry.analyzer.function.TelemetryFunctionManager; 004 import org.hackystat.telemetry.analyzer.language.ast.ChartReference; 005 import org.hackystat.telemetry.analyzer.language.ast.Constant; 006 import org.hackystat.telemetry.analyzer.language.ast.Expression; 007 import org.hackystat.telemetry.analyzer.language.ast.FunctionCall; 008 import org.hackystat.telemetry.analyzer.language.ast.NumberConstant; 009 import org.hackystat.telemetry.analyzer.language.ast.ReducerCall; 010 import org.hackystat.telemetry.analyzer.language.ast.StreamsReference; 011 import org.hackystat.telemetry.analyzer.language.ast.StringConstant; 012 import org.hackystat.telemetry.analyzer.language.ast.TelemetryChartDefinition; 013 import org.hackystat.telemetry.analyzer.language.ast.TelemetryChartYAxisDefinition; 014 import org.hackystat.telemetry.analyzer.language.ast.TelemetryReportDefinition; 015 import org.hackystat.telemetry.analyzer.language.ast.TelemetryStreamsDefinition; 016 import org.hackystat.telemetry.analyzer.language.ast.Variable; 017 import org.hackystat.telemetry.analyzer.language.ast.YAxisReference; 018 import org.hackystat.telemetry.analyzer.model.TelemetryStream; 019 import org.hackystat.telemetry.analyzer.model.TelemetryStreamCollection; 020 import org.hackystat.telemetry.analyzer.reducer.TelemetryReducerManager; 021 import org.hackystat.dailyprojectdata.client.DailyProjectDataClient; 022 import org.hackystat.sensorbase.resource.projects.jaxb.Project; 023 import org.hackystat.utilities.time.interval.Interval; 024 025 /** 026 * Provides an evaluation function for Telemetry, in which a definition is evaluated with 027 * respect to a set of variables and their values, a Project, and an Interval. 028 * 029 * @author (Cedric) Qin ZHANG 030 */ 031 public class TelemetryEvaluator { 032 033 /** 034 * Private constructor to prevent this class from being instantiated. 035 */ 036 private TelemetryEvaluator() { 037 } 038 039 /** 040 * Evaluates a telemetry streams definition to produce a 041 * <code>TelemetryStreamsObject</code> object. 042 * 043 * @param streamsDefinition The telemetry streams definition. 044 * @param variableResolver The variable resolver. 045 * @param project The project. 046 * @param dpdClient The DPD Client. 047 * @param interval The interval. 048 * 049 * @return An instance of a <code>TelemetryStreamsObject</code> object. 050 * 051 * @throws TelemetryEvaluationException If there is any error during the evaluation process. 052 */ 053 public static TelemetryStreamsObject evaluate(TelemetryStreamsDefinition streamsDefinition, 054 VariableResolver variableResolver, Project project, DailyProjectDataClient dpdClient, 055 Interval interval) 056 throws TelemetryEvaluationException { 057 058 Object result = resolveExpression(streamsDefinition.getExpression(), 059 variableResolver, project, dpdClient, interval); 060 if (! (result instanceof TelemetryStreamCollection)) { 061 throw new TelemetryEvaluationException("Telemetry streams " + streamsDefinition.getName() 062 + " does not evaluate to a TelemetryStreamCollection. " 063 + "Is there at least one reducer in the expression?"); 064 } 065 066 // telemetry_streams_definition_name[<parameter, ..., parameter>][:individual_stream_tag]. 067 StringBuffer categorySeriesNamePrefix = new StringBuffer(streamsDefinition.getName()); 068 Variable[] variables = streamsDefinition.getVariables(); 069 if (variables.length > 0) { 070 categorySeriesNamePrefix.append('<'); 071 categorySeriesNamePrefix.append(variableResolver.resolve(variables[0]).getValueString()); 072 for (int i = 1; i < variables.length; i++) { 073 categorySeriesNamePrefix.append(", "); 074 categorySeriesNamePrefix.append(variableResolver.resolve(variables[i]).getValueString()); 075 } 076 categorySeriesNamePrefix.append('>'); 077 } 078 079 TelemetryStreamsObject telemetryStreamsObject = new TelemetryStreamsObject(streamsDefinition); 080 for (TelemetryStream stream : ((TelemetryStreamCollection) result)) { 081 Object streamTag = stream.getTag(); 082 String streamName = streamTag == null ? categorySeriesNamePrefix.toString() 083 : categorySeriesNamePrefix.toString() + ':' + streamTag.toString(); 084 telemetryStreamsObject.addStream(new TelemetryStreamsObject.Stream(streamName, stream)); 085 } 086 return telemetryStreamsObject; 087 } 088 089 /** 090 * Evaluates a telemetry chart definition to <code>TeemetryChartObject</code> object. 091 * 092 * @param chartDefinition The telemetry chart definition. 093 * @param telemetryDefinitionResolver The telemetry definition resolver. 094 * @param variableResolver The variable resolver. 095 * @param project The project. 096 * @param dpdClient The DPD Client. 097 * @param interval The interval. 098 * 099 * @return An instance of <code>TelemetryChartObject</code> object. 100 * 101 * @throws TelemetryEvaluationException If there is any error during the evaluation process. 102 */ 103 public static TelemetryChartObject evaluate(TelemetryChartDefinition chartDefinition, 104 TelemetryDefinitionResolver telemetryDefinitionResolver, VariableResolver variableResolver, 105 Project project, DailyProjectDataClient dpdClient, Interval interval) 106 throws TelemetryEvaluationException { 107 108 TelemetryChartObject telemetryChartObject = new TelemetryChartObject(chartDefinition); 109 110 for (TelemetryChartDefinition.SubChartDefinition subChart : chartDefinition.getSubCharts()) { 111 112 //find the TelmetryStreamsDefinition object 113 StreamsReference streamsRef = subChart.getStreamsReference(); 114 TelemetryStreamsDefinition streamsDef 115 = telemetryDefinitionResolver.resolveStreamsDefinition(streamsRef.getName()); 116 //prepares a variable resolver for the TelemetryStreamsDefinition object 117 Expression[] streamsRefParameters = streamsRef.getParameters(); 118 Variable[] streamsVariables = streamsDef.getVariables(); 119 if (streamsVariables.length != streamsRefParameters.length) { 120 throw new TelemetryEvaluationException("Error in chart definition detected. The streams '" 121 + streamsDef.getName() + "' the chart relies on has " + streamsVariables.length 122 + " parameter(s), but the chart has only supplied " + streamsRefParameters.length 123 + " parameter value(s)."); 124 } 125 VariableResolver streamsVariableResolver = new VariableResolver(); 126 for (int j = 0; j < streamsVariables.length; j++) { 127 Expression streamsRefParameter = streamsRefParameters[j]; 128 if (streamsRefParameter instanceof Constant) { 129 streamsVariableResolver.add(streamsVariables[j], (Constant) streamsRefParameter); 130 } 131 else if (streamsRefParameter instanceof Variable) { 132 Constant constant = variableResolver.resolve((Variable) streamsRefParameter); 133 streamsVariableResolver.add(streamsVariables[j], constant); 134 } 135 else { 136 throw new RuntimeException("Unknow parameter type."); 137 } 138 } 139 //get TelemetryStreamsObject 140 TelemetryStreamsObject streamsObject 141 = TelemetryEvaluator.evaluate(streamsDef, streamsVariableResolver, project, dpdClient, 142 interval); 143 144 145 //find the YAxisDefinition object 146 YAxisReference yAxisRef = subChart.getYAxisReference(); 147 TelemetryChartYAxisDefinition yAxisDef 148 = telemetryDefinitionResolver.resolveYAxisDefinition(yAxisRef.getName()); 149 //prepares a variable resolver for the YAxisDefinition object 150 Expression[] yAxisRefParameters = yAxisRef.getParameters(); 151 Variable[] yAxisVariables = yAxisDef.getVariables(); 152 if (yAxisVariables.length != yAxisRefParameters.length) { 153 throw new TelemetryEvaluationException("Error in chart definition detected. The y-axis '" 154 + yAxisDef.getName() + "' the chart relies on has " + yAxisVariables.length 155 + " parameter(s), but the chart has only supplied " + yAxisRefParameters.length 156 + " parameter value(s)."); 157 } 158 VariableResolver yAxisVariableResolver = new VariableResolver(); 159 for (int j = 0; j < yAxisVariables.length; j++) { 160 Expression yAxisRefParameter = yAxisRefParameters[j]; 161 if (yAxisRefParameter instanceof Constant) { 162 yAxisVariableResolver.add(yAxisVariables[j], (Constant) yAxisRefParameter); 163 } 164 else if (yAxisRefParameter instanceof Variable) { 165 Constant constant = variableResolver.resolve((Variable) yAxisRefParameter); 166 yAxisVariableResolver.add(yAxisVariables[j], constant); 167 } 168 else { 169 throw new RuntimeException("Unsupported parameter type in y-axis " + yAxisDef.getName()); 170 } 171 } 172 //get y-axis label value 173 String yAxisLabelValue = null; 174 Expression yAxisDefLabelParam = yAxisDef.getLabelParameter(); 175 if (yAxisDefLabelParam instanceof StringConstant) { 176 yAxisLabelValue = ((StringConstant) yAxisDefLabelParam).getValue(); 177 } 178 else if (yAxisDefLabelParam instanceof Variable) { 179 Constant constant = yAxisVariableResolver.resolve((Variable) yAxisDefLabelParam); 180 if (constant instanceof StringConstant) { 181 yAxisLabelValue = ((StringConstant) constant).getValue(); 182 } 183 else { 184 throw new TelemetryEvaluationException("Y-axis '" + yAxisDef.getName() 185 + "' variable '" + ((Variable) yAxisDefLabelParam).getName() 186 + "' does not resolve to a string."); 187 } 188 } 189 else { 190 throw new RuntimeException("Unsupported parameter type in y-axis " + yAxisDef.getName()); 191 } 192 193 //add the sub-chart 194 boolean isYAxisInteger = TelemetryChartYAxisDefinition.NUMBER_TYPE_INTEGER.equals( 195 yAxisDef.getNumberType()); 196 Number lowerBound = null; 197 Number upperBound = null; 198 if (! yAxisDef.isAutoScale()) { 199 lowerBound = yAxisDef.getLowerBound(); 200 upperBound = yAxisDef.getUpperBound(); 201 } 202 TelemetryChartObject.YAxis yAxisObject = new TelemetryChartObject.YAxis(yAxisLabelValue, 203 isYAxisInteger, lowerBound, upperBound); 204 telemetryChartObject.addSubChart(new TelemetryChartObject.SubChart( 205 streamsObject, yAxisObject)); 206 } 207 208 return telemetryChartObject; 209 } 210 211 /** 212 * Evaluates a telemetry report definition to <code>TelemetryReportObject</code> object. 213 * 214 * @param reportDefinition The telemetry report definition. 215 * @param telemetryDefinitionResolver The telemetry definition resolver. 216 * @param variableResolver The variable resolver. 217 * @param project The project. 218 * @param dpdClient The DPD Client. 219 * @param interval The interval. 220 * 221 * @return An instance of <code>TelemetryReportObject</code> object. 222 * 223 * @throws TelemetryEvaluationException If there is any error during the evaluation process. 224 */ 225 public static TelemetryReportObject evaluate(TelemetryReportDefinition reportDefinition, 226 TelemetryDefinitionResolver telemetryDefinitionResolver, VariableResolver variableResolver, 227 Project project, DailyProjectDataClient dpdClient, Interval interval) 228 throws TelemetryEvaluationException { 229 230 TelemetryReportObject telemetryReportObject = new TelemetryReportObject(reportDefinition); 231 for (ChartReference chartRef : reportDefinition.getChartReferences()) { 232 //find the TelmetryChartDefinition object 233 TelemetryChartDefinition chartDef 234 = telemetryDefinitionResolver.resolveChartDefinition(chartRef.getName()); 235 236 //prepares a variable resolver for the TelemetryChartDefinition object 237 Expression[] chartRefParameters = chartRef.getParameters(); 238 Variable[] chartVariables = chartDef.getVariables(); 239 if (chartVariables.length != chartRefParameters.length) { 240 throw new TelemetryEvaluationException("Error in report definition detected. The chart '" 241 + chartDef.getName() + "' the chart relies on has " + chartVariables.length 242 + " parameter(s), but the chart has only supplied " + chartRefParameters.length 243 + " parameter value(s)."); 244 } 245 VariableResolver chartVariableResolver = new VariableResolver(); 246 for (int j = 0; j < chartVariables.length; j++) { 247 Expression chartRefParameter = chartRefParameters[j]; 248 if (chartRefParameter instanceof Constant) { 249 chartVariableResolver.add(chartVariables[j], (Constant) chartRefParameter); 250 } 251 else if (chartRefParameter instanceof Variable) { 252 Constant constant = variableResolver.resolve((Variable) chartRefParameter); 253 chartVariableResolver.add(chartVariables[j], constant); 254 } 255 else { 256 throw new RuntimeException("Unknow parameter type."); 257 } 258 } 259 260 //generate telemetry chart object 261 TelemetryChartObject chartObject = TelemetryEvaluator.evaluate(chartDef, 262 telemetryDefinitionResolver, chartVariableResolver, project, dpdClient, interval); 263 telemetryReportObject.addChartObject(chartObject); 264 } 265 266 return telemetryReportObject; 267 } 268 269 /** 270 * Resolves an expression to an instance of <code>TelemetryStreamCollection</code> 271 * or <code>Number</code>. 272 * 273 * @param expression The telemetry expression. 274 * @param variableResolver The variable resolver. 275 * @param project The project. 276 * @param dpdClient The DPD Client. 277 * @param interval The interval. 278 * 279 * @return The resulting instance of type either <code>TelemetryStreamCollection</code> 280 * or <code>Number</code>. 281 * 282 * @throws TelemetryEvaluationException If the expression call cannot be resolved. 283 */ 284 static Object resolveExpression(Expression expression, VariableResolver variableResolver, 285 Project project, DailyProjectDataClient dpdClient, Interval interval) 286 throws TelemetryEvaluationException { 287 try { 288 FunctionCall idempotent = new FunctionCall("idempotent", new Expression[]{expression}); 289 return resolveFunctionCall(idempotent, variableResolver, project, dpdClient, interval); 290 } 291 catch (Exception ex) { 292 throw new TelemetryEvaluationException(ex); 293 } 294 } 295 296 /** 297 * Resolves a function call to an instance of <code>TelemetryStreamCollection</code> 298 * or <code>Number</code>. 299 * 300 * @param functionCall The <code>FunctionCall</code> instance. 301 * @param variableResolver The variable resolver. 302 * @param project The project. 303 * @param dpdClient The DPD Client. 304 * @param interval The interval. 305 * 306 * @return The result. It's an instance of type either <code>TelemetryStreamCollection</code> 307 * or <code>Number</code>. 308 * 309 * @throws Exception If the function call cannot be resolved. 310 */ 311 private static Object resolveFunctionCall(FunctionCall functionCall, 312 VariableResolver variableResolver, Project project, DailyProjectDataClient dpdClient, 313 Interval interval) throws Exception { 314 315 Expression[] parameters = functionCall.getParameters(); 316 317 //Only 3 types of objects are valid: String, Number, TelemetryStreamCollection. 318 Object[] parameterValues = new Object[parameters.length]; 319 320 for (int i = 0; i < parameters.length; i++) { 321 Expression param = parameters[i]; 322 //resolve variable first 323 if (param instanceof Variable) { 324 param = variableResolver.resolve((Variable) param); 325 } 326 //fill parameterValues next 327 if (param instanceof ReducerCall) { 328 parameterValues[i] 329 = resolveReducerCall((ReducerCall) param, variableResolver, project, dpdClient, 330 interval); 331 } 332 else if (param instanceof FunctionCall) { 333 parameterValues[i] 334 = resolveFunctionCall((FunctionCall) param, variableResolver, project, dpdClient, 335 interval); 336 } 337 else if (param instanceof NumberConstant) { 338 parameterValues[i] = ((NumberConstant) param).getValue(); 339 } 340 else if (param instanceof StringConstant) { 341 parameterValues[i] = ((StringConstant) param).getValue(); 342 } 343 else { 344 throw new TelemetryEvaluationException("Function " + functionCall.getFunctionName() 345 + " does not accept parameter of type " + param.getClass().getName()); 346 } 347 } 348 return TelemetryFunctionManager.getInstance().compute(functionCall.getFunctionName(), 349 parameterValues); 350 } 351 352 353 /** 354 * Resolves a reducer call to an instance of <code>TelemetryStreamCollection</code>. 355 * 356 * @param reducerCall The <code>ReducerCall</code> instance. 357 * @param variableResolver The variable resolver. 358 * @param project The project. 359 * @param dpdClient The DPD Client. 360 * @param interval The interval. 361 * 362 * @return The resulting instance of <code>TelemetryStreamCollection</code>. 363 * 364 * @throws Exception If the reducer call cannot be resolved. 365 */ 366 private static TelemetryStreamCollection resolveReducerCall(ReducerCall reducerCall, 367 VariableResolver variableResolver, Project project, DailyProjectDataClient dpdClient, 368 Interval interval) throws Exception { 369 370 Expression[] parameters = reducerCall.getParameters(); 371 372 //Only objects of type String is valid. 373 String[] parameterValues = new String[parameters.length]; 374 375 for (int i = 0; i < parameters.length; i++) { 376 Expression param = parameters[i]; 377 //resolve variable first 378 if (param instanceof Variable) { 379 param = variableResolver.resolve((Variable) param); 380 } 381 //fill parameterValues next 382 if (param instanceof Constant) { 383 parameterValues[i] = ((Constant) param).getValueString(); 384 } 385 else { 386 throw new TelemetryEvaluationException("Reducer " + reducerCall.getReducerName() 387 + " does not accept parameter of type " + param.getClass().getName()); 388 } 389 } 390 return TelemetryReducerManager.getInstance().compute(reducerCall.getReducerName(), 391 dpdClient, project, interval, parameterValues); 392 } 393 }