package ai.h2o.automl;
import hex.Model;
import org.apache.http.client.fluent.Request;
import org.apache.http.entity.ContentType;
import water.util.Log;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class EckoClient {
private static int numEvents = 0;
private static Map<String, ProjectStatus> statuses = new HashMap<>(); // remember the last status for each project
private static final int eckoTimeout = 10000;
private static final String eckoHelpMessage = "Ecko server is NOT enabled for user feedback updates. To use Ecko, start a server on localhost:55555 before you run your AutoML-enabled h2o.";
private static final String eckoFailedMessage = "Ecko server failed. Disabling.";
private static final String eckoExceptionMessage = "Caught exception trying to communicate with the Ecko server: ";
private static final String eckoHost = "http://localhost:55555/";
private static boolean eckoEnabled = true;
private static boolean eckoInitialized = false;
private static final SimpleDateFormat timestampFormat = new SimpleDateFormat("HH:mm:ss.SSS");
private static final String feedbackCellStyle = "-font-size 0.8em";
private static final String feedbackRowStyle = " set Feedback Timestamp "
+ feedbackCellStyle
+ " set Feedback Level "
+ feedbackCellStyle
+ " set Feedback Stage "
+ feedbackCellStyle
+ " set Feedback Message "
+ feedbackCellStyle;
private static final String feedbackTableStyle = " set \"User Feedback\" -font-weight 120";
private static final String projectPageDefs = "at %s def Feedback Timestamp Level Stage Message" +
// " def LeaderboardModel ID Metric" +
// " add Error in vis input tsv mark point x Rank x-type O y Error y-type Q" +
// " add Error in vis input tsv mark point y Error y-type O" +
" add Leaderboard in table" +
" add \"User Feedback\" as map of Feedback" +
feedbackTableStyle +
feedbackRowStyle;
private static final String projectTableStyle = " set Projects -font-weight 120";
private static final String homePageDefs = "at / def BuildStatus Project Message PercentDone Leader" +
" add Projects as map of BuildStatus" +
projectTableStyle;
private final static class ProjectStatus {
public ProjectStatus(String project) {
this.project = project;
}
final String project;
public UserFeedbackEvent lastEvent;
public double progress;
public Leaderboard leaderboard;
public Model leader;
public String leaderMetric;
public double leaderError;
}
private static void initializeEcko() {
int httpStatus = -1;
try {
httpStatus = Request.Put(eckoHost)
.connectTimeout(eckoTimeout)
.socketTimeout(eckoTimeout)
.bodyString(homePageDefs, ContentType.TEXT_PLAIN)
.execute()
.returnResponse()
.getStatusLine()
.getStatusCode();
}
catch (Exception e) {
// handle below
}
if (httpStatus == 200) {
Log.info("Ecko server is enabled for user feedback updates. Open http://localhost:55555 in your browser.");
eckoInitialized = true;
} else {
eckoEnabled = false;
}
if (!eckoEnabled)
Log.info(eckoHelpMessage);
}
private static void initializeProjectPage(String project) {
if (!eckoEnabled || statuses.containsKey(project)) return;
int httpStatus = -1;
statuses.put(project, new ProjectStatus(project));
try {
httpStatus = Request.Put(eckoHost)
.connectTimeout(eckoTimeout)
.socketTimeout(eckoTimeout)
.bodyString(String.format(projectPageDefs, project), ContentType.TEXT_PLAIN)
.execute()
.returnResponse()
.getStatusLine()
.getStatusCode();
}
catch (Exception e) {
// handle below
}
if (! (httpStatus == 200)) {
eckoEnabled = false;
}
if (!eckoEnabled)
Log.info(eckoFailedMessage);
}
public static final void addEvent(UserFeedbackEvent event) {
if (eckoEnabled && !eckoInitialized) {
initializeEcko(); // NOTE: can set eckoEnabled to false
}
if (eckoEnabled) {
AutoML autoML = event.getAutoML();
String project = autoML.project();
initializeProjectPage(project);
ProjectStatus status = statuses.get(statuses.get(event.getAutoML().project()));
status.lastEvent = event;
int httpStatus = -1;
try {
httpStatus = Request.Put(eckoHost)
.connectTimeout(eckoTimeout)
.socketTimeout(eckoTimeout)
.bodyString("at %s put \"User Feedback\" ".format(project) +
String.format("%1$05d", numEvents++) + " " +
timestampFormat.format(new Date(event.getTimestamp())) + " " +
event.getLevel() + " " +
event.getStage() + " " +
"\"" + event.getMessage() + "\"",
ContentType.TEXT_PLAIN)
.execute()
.returnResponse()
.getStatusLine()
.getStatusCode();
} catch (Exception e) {
Log.info(eckoExceptionMessage + e);
}
if (httpStatus == 200) {
// silent
} else {
eckoEnabled = false;
Log.info(eckoFailedMessage);
}
updateHomePage(status);
} // eckoEnabled
} // addEvent
public static final void updateLeaderboard(Leaderboard leaderboard) {
if (eckoEnabled && !eckoInitialized) {
initializeEcko(); // NOTE: can set eckoEnabled to false
}
if (eckoEnabled) {
String project = leaderboard.getProject();
initializeProjectPage(project);
ProjectStatus status = statuses.get(project);
status.leaderboard = leaderboard;
status.leader = leaderboard.getLeader();
status.leaderError = leaderboard.defaultMetricForModel(status.leader)[0]; //First value is sort metric
status.leaderMetric = leaderboard.defaultMetricNameForModel(status.leader)[0]; //First value is sort metric
//String leaderboardTsv = leaderboard.toString(project, leaderboard.getModels(), "\\t", "\\n", false, true, true);
String leaderboardTsv = leaderboard.toString(project, leaderboard.getModels(), "\\t", "\\n", false, true);
String rankTsv = leaderboard.rankTsv();
String timeTsv = leaderboard.timeTsv();
int httpStatus = -1;
try {
httpStatus = Request.Put(eckoHost)
.connectTimeout(eckoTimeout)
.socketTimeout(eckoTimeout)
.bodyString(
"at %s put Leaderboard \"".format(project) +
leaderboardTsv + "\"" +
// "at / put Error \"" +
// rankTsv + "\"",
"",
ContentType.TEXT_PLAIN)
.execute()
.returnResponse()
.getStatusLine()
.getStatusCode();
} catch (Exception e) {
Log.info(eckoExceptionMessage + e);
}
if (httpStatus == 200) {
// silent
} else {
eckoEnabled = false;
Log.info(eckoFailedMessage);
}
updateHomePage(status);
} // eckoEnabled
} // updateLeaderboard
public static final void updateProgress(AutoML autoML) {
ProjectStatus status = statuses.get(autoML.project());
double progress = (autoML.job() == null ? 0.0 : autoML.job().progress());
status.progress = progress;
updateHomePage(status);
}
public static final void updateHomePage(ProjectStatus status) {
if (eckoEnabled && !eckoInitialized) {
initializeEcko(); // NOTE: can set eckoEnabled to false
}
String project = status.project;
if (eckoEnabled) {
int httpStatus = -1;
try {
httpStatus = Request.Put(eckoHost)
.connectTimeout(eckoTimeout)
.socketTimeout(eckoTimeout)
.bodyString(
"at %s put Leaderboard \"".format(project) +
// leaderboardTsv + "\"" +
// "at / put Error \"" +
// rankTsv + "\"",
"",
ContentType.TEXT_PLAIN)
.execute()
.returnResponse()
.getStatusLine()
.getStatusCode();
} catch (Exception e) {
Log.info(eckoExceptionMessage + e);
}
if (httpStatus == 200) {
// silent
} else {
eckoEnabled = false;
Log.info(eckoFailedMessage);
}
} // eckoEnabled
} // updateLeaderboard
}