From d16f86f7abb684d9ee12df4005f5daac69e6c927 Mon Sep 17 00:00:00 2001 From: David Roberts Date: Thu, 4 Apr 2019 10:43:30 +0100 Subject: [PATCH] [ML] Add created_by info to usage stats (#40518) This change adds information about which UI path (if any) created ML anomaly detector jobs to the stats returned by the _xpack/usage endpoint. Counts for the following possibilities are expected: * ml_module_apache_access * ml_module_apm_transaction * ml_module_auditbeat_process_docker * ml_module_auditbeat_process_hosts * ml_module_nginx_access * ml_module_sample * multi_metric_wizard * population_wizard * single_metric_wizard * unknown The "unknown" count is for jobs that do not have a created_by setting in their custom_settings. Closes #38403 --- .../ml/MachineLearningFeatureSetUsage.java | 1 + .../xpack/ml/MachineLearningFeatureSet.java | 27 +++++++++++++++---- .../ml/MachineLearningFeatureSetTests.java | 16 ++++++++++- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/MachineLearningFeatureSetUsage.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/MachineLearningFeatureSetUsage.java index f3fb443673d..755d6faef0b 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/MachineLearningFeatureSetUsage.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/MachineLearningFeatureSetUsage.java @@ -25,6 +25,7 @@ public class MachineLearningFeatureSetUsage extends XPackFeatureSet.Usage { public static final String DETECTORS = "detectors"; public static final String FORECASTS = "forecasts"; public static final String MODEL_SIZE = "model_size"; + public static final String CREATED_BY = "created_by"; public static final String NODE_COUNT = "node_count"; private final Map jobsUsage; diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/MachineLearningFeatureSet.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/MachineLearningFeatureSet.java index 953f75801c7..c913babbaa4 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/MachineLearningFeatureSet.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/MachineLearningFeatureSet.java @@ -216,13 +216,16 @@ public class MachineLearningFeatureSet implements XPackFeatureSet { Map detectorStatsByState = new HashMap<>(); Map modelSizeStatsByState = new HashMap<>(); Map forecastStatsByState = new HashMap<>(); + Map> createdByByState = new HashMap<>(); List jobsStats = response.getResponse().results(); Map jobMap = jobs.stream().collect(Collectors.toMap(Job::getId, item -> item)); + Map allJobsCreatedBy = jobs.stream().map(this::jobCreatedBy) + .collect(Collectors.groupingBy(item -> item, Collectors.counting()));; for (GetJobsStatsAction.Response.JobStats jobStats : jobsStats) { ModelSizeStats modelSizeStats = jobStats.getModelSizeStats(); - int detectorsCount = jobMap.get(jobStats.getJobId()).getAnalysisConfig() - .getDetectors().size(); + Job job = jobMap.get(jobStats.getJobId()); + int detectorsCount = job.getAnalysisConfig().getDetectors().size(); double modelSize = modelSizeStats == null ? 0.0 : jobStats.getModelSizeStats().getModelBytes(); @@ -237,27 +240,41 @@ public class MachineLearningFeatureSet implements XPackFeatureSet { modelSizeStatsByState.computeIfAbsent(jobState, js -> new StatsAccumulator()).add(modelSize); forecastStatsByState.merge(jobState, jobStats.getForecastStats(), (f1, f2) -> f1.merge(f2)); + createdByByState.computeIfAbsent(jobState, js -> new HashMap<>()) + .compute(jobCreatedBy(job), (k, v) -> (v == null) ? 1L : (v + 1)); } jobsUsage.put(MachineLearningFeatureSetUsage.ALL, createJobUsageEntry(jobs.size(), allJobsDetectorsStats, - allJobsModelSizeStats, allJobsForecastStats)); + allJobsModelSizeStats, allJobsForecastStats, allJobsCreatedBy)); for (JobState jobState : jobCountByState.keySet()) { jobsUsage.put(jobState.name().toLowerCase(Locale.ROOT), createJobUsageEntry( jobCountByState.get(jobState).get(), detectorStatsByState.get(jobState), modelSizeStatsByState.get(jobState), - forecastStatsByState.get(jobState))); + forecastStatsByState.get(jobState), + createdByByState.get(jobState))); } } + private String jobCreatedBy(Job job) { + Map customSettings = job.getCustomSettings(); + if (customSettings == null || customSettings.containsKey(MachineLearningFeatureSetUsage.CREATED_BY) == false) { + return "unknown"; + } + // Replace non-alpha-numeric characters with underscores because + // the values from custom settings become keys in the usage data + return customSettings.get(MachineLearningFeatureSetUsage.CREATED_BY).toString().replaceAll("\\W", "_"); + } + private Map createJobUsageEntry(long count, StatsAccumulator detectorStats, StatsAccumulator modelSizeStats, - ForecastStats forecastStats) { + ForecastStats forecastStats, Map createdBy) { Map usage = new HashMap<>(); usage.put(MachineLearningFeatureSetUsage.COUNT, count); usage.put(MachineLearningFeatureSetUsage.DETECTORS, detectorStats.asMap()); usage.put(MachineLearningFeatureSetUsage.MODEL_SIZE, modelSizeStats.asMap()); usage.put(MachineLearningFeatureSetUsage.FORECASTS, forecastStats.asMap()); + usage.put(MachineLearningFeatureSetUsage.CREATED_BY, createdBy); return usage; } diff --git a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/MachineLearningFeatureSetTests.java b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/MachineLearningFeatureSetTests.java index f297bf2e9c7..df447d7ec6c 100644 --- a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/MachineLearningFeatureSetTests.java +++ b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/MachineLearningFeatureSetTests.java @@ -155,7 +155,8 @@ public class MachineLearningFeatureSetTests extends ESTestCase { Settings.Builder settings = Settings.builder().put(commonSettings); settings.put("xpack.ml.enabled", true); - Job opened1 = buildJob("opened1", Arrays.asList(buildMinDetector("foo"))); + Job opened1 = buildJob("opened1", Collections.singletonList(buildMinDetector("foo")), + Collections.singletonMap("created_by", randomFrom("a-cool-module", "a_cool_module", "a cool module"))); GetJobsStatsAction.Response.JobStats opened1JobStats = buildJobStats("opened1", JobState.OPENED, 100L, 3L); Job opened2 = buildJob("opened2", Arrays.asList(buildMinDetector("foo"), buildMinDetector("bar"))); GetJobsStatsAction.Response.JobStats opened2JobStats = buildJobStats("opened2", JobState.OPENED, 200L, 8L); @@ -200,6 +201,8 @@ public class MachineLearningFeatureSetTests extends ESTestCase { assertThat(source.getValue("jobs._all.model_size.max"), equalTo(300.0)); assertThat(source.getValue("jobs._all.model_size.total"), equalTo(600.0)); assertThat(source.getValue("jobs._all.model_size.avg"), equalTo(200.0)); + assertThat(source.getValue("jobs._all.created_by.a_cool_module"), equalTo(1)); + assertThat(source.getValue("jobs._all.created_by.unknown"), equalTo(2)); assertThat(source.getValue("jobs.opened.count"), equalTo(2)); assertThat(source.getValue("jobs.opened.detectors.min"), equalTo(1.0)); @@ -210,6 +213,8 @@ public class MachineLearningFeatureSetTests extends ESTestCase { assertThat(source.getValue("jobs.opened.model_size.max"), equalTo(200.0)); assertThat(source.getValue("jobs.opened.model_size.total"), equalTo(300.0)); assertThat(source.getValue("jobs.opened.model_size.avg"), equalTo(150.0)); + assertThat(source.getValue("jobs.opened.created_by.a_cool_module"), equalTo(1)); + assertThat(source.getValue("jobs.opened.created_by.unknown"), equalTo(1)); assertThat(source.getValue("jobs.closed.count"), equalTo(1)); assertThat(source.getValue("jobs.closed.detectors.min"), equalTo(3.0)); @@ -220,6 +225,8 @@ public class MachineLearningFeatureSetTests extends ESTestCase { assertThat(source.getValue("jobs.closed.model_size.max"), equalTo(300.0)); assertThat(source.getValue("jobs.closed.model_size.total"), equalTo(300.0)); assertThat(source.getValue("jobs.closed.model_size.avg"), equalTo(300.0)); + assertThat(source.getValue("jobs.closed.created_by.a_cool_module"), is(nullValue())); + assertThat(source.getValue("jobs.closed.created_by.unknown"), equalTo(1)); assertThat(source.getValue("jobs.opening"), is(nullValue())); assertThat(source.getValue("jobs.closing"), is(nullValue())); @@ -359,6 +366,7 @@ public class MachineLearningFeatureSetTests extends ESTestCase { }).when(jobManager).expandJobs(eq(MetaData.ALL), eq(true), any(ActionListener.class)); doAnswer(invocationOnMock -> { + @SuppressWarnings("unchecked") ActionListener listener = (ActionListener) invocationOnMock.getArguments()[2]; listener.onResponse(new GetJobsStatsAction.Response( @@ -400,6 +408,7 @@ public class MachineLearningFeatureSetTests extends ESTestCase { private void givenDatafeeds(List datafeedStats) { doAnswer(invocationOnMock -> { + @SuppressWarnings("unchecked") ActionListener listener = (ActionListener) invocationOnMock.getArguments()[2]; listener.onResponse(new GetDatafeedsStatsAction.Response( @@ -416,10 +425,15 @@ public class MachineLearningFeatureSetTests extends ESTestCase { } private static Job buildJob(String jobId, List detectors) { + return buildJob(jobId, detectors, null); + } + + private static Job buildJob(String jobId, List detectors, Map customSettings) { AnalysisConfig.Builder analysisConfig = new AnalysisConfig.Builder(detectors); return new Job.Builder(jobId) .setAnalysisConfig(analysisConfig) .setDataDescription(new DataDescription.Builder()) + .setCustomSettings(customSettings) .build(new Date(randomNonNegativeLong())); }