{"action":"created","comment":{"url":"https://api.github.com/repos/linkedin/cruise-control/pulls/comments/168940572","pull_request_review_id":97392650,"id":168940572,"diff_hunk":"@@ -0,0 +1,474 @@\n+/*\n+ * Copyright 2017 LinkedIn Corp. Licensed under the BSD 2-Clause License (the \"License\"). See License in the project root for license information.\n+ */\n+\n+package com.linkedin.cruisecontrol.monitor.sampling.aggregator;\n+\n+import com.linkedin.cruisecontrol.common.LongGenerationed;\n+import com.linkedin.cruisecontrol.exception.NotEnoughValidWindowsException;\n+import com.linkedin.cruisecontrol.metricdef.MetricDef;\n+import com.linkedin.cruisecontrol.model.Entity;\n+import com.linkedin.cruisecontrol.monitor.sampling.MetricSample;\n+import java.util.ArrayList;\n+import java.util.Collections;\n+import java.util.HashSet;\n+import java.util.List;\n+import java.util.Map;\n+import java.util.Set;\n+import java.util.SortedSet;\n+import java.util.concurrent.ConcurrentHashMap;\n+import java.util.concurrent.ConcurrentMap;\n+import java.util.concurrent.locks.ReentrantLock;\n+import org.slf4j.Logger;\n+import org.slf4j.LoggerFactory;\n+\n+\n+/**\n+ * Another metric sample aggregator which has less memory consumption.\n+ *\n+ * <p>This class is thread safe.</p>\n+ *\n+ * @param <G> The aggregation entity group class. Note that the entity group will be used as a key to HashMaps,\n+ *           so it must have a valid {@link Object#hashCode()} and {@link Object#equals(Object)} implementation.\n+ * @param <E> The entity class. Note that the entity will be used as a key to HashMaps, so it must have\n+ *           a valid {@link Object#hashCode()} and {@link Object#equals(Object)} implementation.\n+ */\n+public class MetricSampleAggregator<G, E extends Entity<G>> extends LongGenerationed {\n+  private static final Logger LOG = LoggerFactory.getLogger(MetricSampleAggregator.class);\n+\n+  private final ConcurrentMap<E, RawMetricValues> _rawMetrics;\n+  private final MetricSampleAggregatorState<G, E> _aggregatorState;\n+  private final ReentrantLock _windowRollingLock;\n+\n+  protected final ConcurrentMap<E, E> _identityEntityMap;\n+  protected final int _numWindows;\n+  protected final int _minSamplesPerWindow;\n+  protected final int _numWindowsToKeep;\n+  protected final long _windowMs;\n+  protected final int _maxAllowedImputations;\n+  protected final MetricDef _metricDef;\n+\n+  private volatile long _currentWindowIndex;\n+  private volatile long _oldestWindowIndex;\n+\n+  /**\n+   * Construct the abstract metric sample aggregator.\n+   *\n+   * @param numWindows the number of snapshot windows needed.\n+   * @param windowMs the size of each snapshot window in milliseconds\n+   * @param minSamplesPerWindow minimum samples per snapshot window.\n+   * @param maxAllowedImputations the maximum allowed number of imputations for an entity if some windows miss data.\n+   * @param completenessCacheSize the completeness cache size, i.e. the number of recent completeness query result to\n+   *                              cache.\n+   * @param metricDef metric definitions.\n+   */\n+  public MetricSampleAggregator(int numWindows,\n+                                long windowMs,\n+                                int minSamplesPerWindow,\n+                                int maxAllowedImputations,\n+                                int completenessCacheSize,\n+                                MetricDef metricDef) {\n+    super(0);\n+    _identityEntityMap = new ConcurrentHashMap<>();\n+    _rawMetrics = new ConcurrentHashMap<>();\n+    _numWindows = numWindows;\n+    _windowMs = windowMs;\n+    // We keep one more window for the active window.\n+    _numWindowsToKeep = _numWindows + 1;\n+    _minSamplesPerWindow = minSamplesPerWindow;\n+    _windowRollingLock = new ReentrantLock();\n+    _maxAllowedImputations = maxAllowedImputations;\n+    _metricDef = metricDef;\n+    _aggregatorState = new MetricSampleAggregatorState<>(_generation.get(), _windowMs, completenessCacheSize);\n+    _oldestWindowIndex = 0L;\n+    _currentWindowIndex = 0L;\n+  }\n+\n+  /**\n+   * Add a sample to the metric aggregator.\n+   *\n+   * @param sample The metric sample to add.\n+   *\n+   * @return true if the sample is accepted, false if the sample is ignored.\n+   */\n+  public boolean addSample(MetricSample<G, E> sample) {\n+    if (!sample.isValid(_metricDef)) {\n+      LOG.warn(\"The metric sample is discarded due to missing metrics. Sample: {}\", sample);\n+      return false;\n+    }\n+    long windowIndex = windowIndex(sample.sampleTime());\n+    // Skip the samples that are too old.\n+    if (windowIndex < _oldestWindowIndex) {\n+      return false;\n+    }\n+    boolean newWindowRolledOut = rollOutNewWindowIfNeeded(windowIndex);\n+    RawMetricValues rawMetricValues =\n+        _rawMetrics.computeIfAbsent(identity(sample.entity()), k -> {\n+          RawMetricValues rawValues = new RawMetricValues(_numWindowsToKeep, _minSamplesPerWindow);\n+          rawValues.updateOldestWindowIndex(_oldestWindowIndex);\n+          return rawValues;\n+        });\n+    LOG.trace(\"Adding sample {} to window index {}\", sample, windowIndex);\n+    rawMetricValues.addSample(sample, windowIndex, _metricDef);\n+    if (newWindowRolledOut || windowIndex != _currentWindowIndex) {\n+      long generation = _generation.incrementAndGet();\n+      // Data has been inserted to an old window.\n+      if (windowIndex != _currentWindowIndex) {\n+        _aggregatorState.updateWindowGeneration(windowIndex, generation);\n+      }\n+    }\n+    return true;\n+  }\n+\n+  /**\n+   * Aggregate the metric samples in the given period into a {@link MetricSampleAggregationResult} based on the\n+   * specified {@link AggregationOptions}.\n+   *\n+   * <p>\n+   *   The aggregation result contains all the entities in {@link AggregationOptions#interestedEntities()}.\n+   *   For the entities that are completely missing, an empty result is added with all the value set to 0.0 and\n+   *   all the window indexes marked as {@link Imputation#NO_VALID_IMPUTATION}.\n+   * </p>\n+   *\n+   * @param from the starting timestamp of the aggregation period in milliseconds.\n+   * @param to the end timestamp of the aggregation period in milliseconds.\n+   * @param options the {@link AggregationOptions} used to perform the aggregation.\n+   * @return An {@link MetricSampleAggregationResult} which contains the\n+   * @throws NotEnoughValidWindowsException\n+   */\n+  public MetricSampleAggregationResult<G, E> aggregate(long from, long to, AggregationOptions<G, E> options)\n+      throws NotEnoughValidWindowsException {\n+    // prevent window rolling.\n+    _windowRollingLock.lock();\n+    try {\n+      // Ensure the range is valid.\n+      long fromWindowIndex = Math.max(windowIndex(from), _oldestWindowIndex);\n+      long toWindowIndex = Math.min(windowIndex(to), _currentWindowIndex - 1);\n+      if (fromWindowIndex > _currentWindowIndex || toWindowIndex < _oldestWindowIndex) {\n+        throw new NotEnoughValidWindowsException(String.format(\"There is no window available in range [%d, %d]\",\n+                                                               from, to));\n+      }\n+\n+      // Get and verify the completeness.\n+      updateAggregatorStateIfNeeded();\n+      AggregationOptions<G, E> interpretedOptions = interpretAggregationOptions(options);\n+      MetricSampleCompleteness<G, E> completeness =\n+          _aggregatorState.completeness(fromWindowIndex, toWindowIndex, interpretedOptions);\n+      // We use the original time from and to here because they are only for logging purpose.\n+      validateCompleteness(from, to, completeness, options);\n+\n+      // Perform the aggregation.\n+      List<Long> windows = toWindows(completeness.validWindowIndexes());\n+      MetricSampleAggregationResult<G, E> result = new MetricSampleAggregationResult<>(generation(), completeness);\n+      Set<E> entitiesToInclude =\n+          options.includeInvalidEntities() ? interpretedOptions.interestedEntities() : completeness.coveredEntities();\n+      for (E entity : entitiesToInclude) {\n+        RawMetricValues rawValues = _rawMetrics.get(entity);\n+        if (rawValues == null) {\n+          ValuesAndImputations valuesAndImputations = ValuesAndImputations.empty(completeness.validWindowIndexes().size(), _metricDef);\n+          valuesAndImputations.setWindows(windows);\n+          result.addResult(entity, valuesAndImputations);\n+          result.recordInvalidEntity(entity);\n+        } else {\n+          ValuesAndImputations valuesAndImputations = rawValues.aggregate(completeness.validWindowIndexes(), _metricDef);\n+          valuesAndImputations.setWindows(windows);\n+          result.addResult(entity, valuesAndImputations);\n+          if (!rawValues.isValid(_maxAllowedImputations)) {\n+            result.recordInvalidEntity(entity);\n+          }\n+        }\n+      }\n+      return result;\n+    } finally {\n+      _windowRollingLock.unlock();\n+    }\n+  }\n+\n+  /**\n+   * Get the {@link MetricSampleCompleteness} of the MetricSampleAggregator with the given {@link AggregationOptions}\n+   * for a given period of time. The current active window is excluded.\n+   *\n+   * @param from starting time of the period to check.\n+   * @param to ending time of the period to check.\n+   * @param options the {@link AggregationOptions} to use for the completeness check.\n+   * @return the {@link MetricSampleCompleteness} of the MetricSampleAggregator.\n+   */\n+  public MetricSampleCompleteness<G, E> completeness(long from, long to, AggregationOptions<G, E> options) {\n+    _windowRollingLock.lock();\n+    try {\n+      long fromWindowIndex = Math.max(windowIndex(from), _oldestWindowIndex);\n+      long toWindowIndex = Math.min(windowIndex(to), _currentWindowIndex - 1);\n+      if (fromWindowIndex > _currentWindowIndex || toWindowIndex < _oldestWindowIndex) {\n+        return new MetricSampleCompleteness<>(generation(), _windowMs);\n+      }\n+      updateAggregatorStateIfNeeded();\n+      return _aggregatorState.completeness(windowIndex(from), windowIndex(to), interpretAggregationOptions(options));\n+    } finally {\n+      _windowRollingLock.unlock();\n+    }\n+  }\n+\n+  /**\n+   * Get a list of available windows in the MetricSampleAggregator. The available windows may include the windows\n+   * that do not have any metric samples. It is just a consecutive list of windows starting from the oldest window\n+   * that hasn't been evicted (inclusive) until the current active window (exclusive).\n+   *\n+   * @return a list of available windows in the MetricSampleAggregator.\n+   */\n+  public List<Long> availableWindows() {\n+    return getWindowList(_oldestWindowIndex, _currentWindowIndex - 1);\n+  }\n+\n+  /**\n+   * @return the number of available windows in the MetricSampleAggregator.\n+   */\n+  public int numAvailableWindows() {\n+    return numAvailableWindows(-1, Long.MAX_VALUE);\n+  }\n+\n+  /**\n+   * Get the number of available windows in the given time range, excluding the current active window.\n+   *\n+   * @param from the starting time of the time range. (inclusive)\n+   * @param to the end time of the time range. (inclusive)\n+   * @return the number of the windows in the given time range.\n+   */\n+  public int numAvailableWindows(long from, long to) {\n+    long fromWindowIndex = Math.max(windowIndex(from), _oldestWindowIndex);\n+    long toWindowIndex = Math.min(windowIndex(to), _currentWindowIndex - 1);\n+    return Math.max(0, (int) (toWindowIndex - fromWindowIndex + 1));\n+  }\n+\n+  /**\n+   * @return all the windows in the MetricSampleAggregator, including the current active window.\n+   */\n+  public List<Long> allWindows() {\n+    return getWindowList(_oldestWindowIndex, _currentWindowIndex);\n+  }\n+\n+  /**\n+   * @return the earliest available window in the MetricSampleAggregator. Null is returned if there is\n+   * no window available at all.\n+   */\n+  public Long earliestWindow() {\n+    return _rawMetrics.isEmpty() ? null : _oldestWindowIndex * _windowMs;\n+  }\n+\n+  /**\n+   * Get the total number of samples that is currently aggregated by the MetricSampleAggregator. The number\n+   * does not include the samples in the windows that have already been evicted.\n+   *\n+   * @return the number of samples aggregated by the MetricSampleAggregator.\n+   */\n+  public int numSamples() {\n+    int count = 0;\n+    for (RawMetricValues rawValues : _rawMetrics.values()) {\n+      count += rawValues.numSamples();\n+    }\n+    return count;\n+  }\n+\n+  /**\n+   * Keep the given set of entities in the MetricSampleAggregator and remove the rest of the entities.\n+   *\n+   * @param entities the entities to retain.\n+   */\n+  public void retainEntities(Set<E> entities) {\n+    _rawMetrics.entrySet().removeIf(entry -> !entities.contains(entry.getKey()));\n+    _generation.incrementAndGet();\n+  }\n+\n+  /**\n+   * remove the given set of entities from the MetricSampleAggregator.\n+   * @param entities the entities to remove.\n+   */\n+  public void removeEntities(Set<E> entities) {\n+    _rawMetrics.entrySet().removeIf(entry -> entities.contains(entry.getKey()));\n+    _generation.incrementAndGet();\n+  }\n+\n+  /**\n+   * Keep the give set of entity groups in the MetricSampleAggregator and remove the reset of the\n+   * entity groups.\n+   * @param entityGroups the entity groups to retain.\n+   */\n+  public void retainEntityGroup(Set<G> entityGroups) {\n+    _rawMetrics.entrySet().removeIf(entry -> !entityGroups.contains(entry.getKey().group()));\n+    _generation.incrementAndGet();\n+  }\n+\n+  /**\n+   * Remove the given set of entity groups from the MetricSampleAggregator.\n+   *\n+   * @param entityGroups the entity groups to remove from the MetricSampleAggregator.\n+   */\n+  public void removeEntityGroup(Set<G> entityGroups) {\n+    _rawMetrics.entrySet().removeIf(entry -> entityGroups.contains(entry.getKey().group()));\n+    _generation.incrementAndGet();\n+  }\n+\n+  /**\n+   * Clear the MetricSampleAggregator.\n+   */\n+  public void clear() {\n+    _windowRollingLock.lock();\n+    try {\n+      _rawMetrics.clear();\n+      _aggregatorState.clear();\n+      _generation.incrementAndGet();\n+    } finally {\n+      _windowRollingLock.unlock();\n+    }\n+  }\n+\n+  // package private for testing.\n+  MetricSampleAggregatorState<G, E> aggregatorState() {\n+    updateAggregatorStateIfNeeded();\n+    return _aggregatorState;\n+  }\n+\n+  // both from and to window indexes are inclusive.","path":"cruise-control-core/src/main/java/com/linkedin/cruisecontrol/monitor/sampling/aggregator/MetricSampleAggregator.java","position":330,"original_position":330,"commit_id":"b5f6ba84b5bcbf4a9673db85391b86bf14bab9f4","original_commit_id":"b5f6ba84b5bcbf4a9673db85391b86bf14bab9f4","user":{"login":"becketqin","id":5778611,"avatar_url":"https://avatars3.githubusercontent.com/u/5778611?v=4","gravatar_id":"","url":"https://api.github.com/users/becketqin","html_url":"https://github.com/becketqin","followers_url":"https://api.github.com/users/becketqin/followers","following_url":"https://api.github.com/users/becketqin/following{/other_user}","gists_url":"https://api.github.com/users/becketqin/gists{/gist_id}","starred_url":"https://api.github.com/users/becketqin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/becketqin/subscriptions","organizations_url":"https://api.github.com/users/becketqin/orgs","repos_url":"https://api.github.com/users/becketqin/repos","events_url":"https://api.github.com/users/becketqin/events{/privacy}","received_events_url":"https://api.github.com/users/becketqin/received_events","type":"User","site_admin":false},"body":"Same as above.","created_at":"2018-02-18T05:46:33Z","updated_at":"2018-02-18T05:46:33Z","html_url":"https://github.com/linkedin/cruise-control/pull/64#discussion_r168940572","pull_request_url":"https://api.github.com/repos/linkedin/cruise-control/pulls/64","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/linkedin/cruise-control/pulls/comments/168940572"},"html":{"href":"https://github.com/linkedin/cruise-control/pull/64#discussion_r168940572"},"pull_request":{"href":"https://api.github.com/repos/linkedin/cruise-control/pulls/64"}},"in_reply_to_id":168018805},"pull_request":{"url":"https://api.github.com/repos/linkedin/cruise-control/pulls/64","id":147635923,"html_url":"https://github.com/linkedin/cruise-control/pull/64","diff_url":"https://github.com/linkedin/cruise-control/pull/64.diff","patch_url":"https://github.com/linkedin/cruise-control/pull/64.patch","issue_url":"https://api.github.com/repos/linkedin/cruise-control/issues/64","number":64,"state":"open","locked":false,"title":"Refactor MetricSampleAggregator to make it more general - Part 1","user":{"login":"becketqin","id":5778611,"avatar_url":"https://avatars3.githubusercontent.com/u/5778611?v=4","gravatar_id":"","url":"https://api.github.com/users/becketqin","html_url":"https://github.com/becketqin","followers_url":"https://api.github.com/users/becketqin/followers","following_url":"https://api.github.com/users/becketqin/following{/other_user}","gists_url":"https://api.github.com/users/becketqin/gists{/gist_id}","starred_url":"https://api.github.com/users/becketqin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/becketqin/subscriptions","organizations_url":"https://api.github.com/users/becketqin/orgs","repos_url":"https://api.github.com/users/becketqin/repos","events_url":"https://api.github.com/users/becketqin/events{/privacy}","received_events_url":"https://api.github.com/users/becketqin/received_events","type":"User","site_admin":false},"body":"This is the first part of a series of patches to make CC more general. This particular patch does the following:\r\n\r\n1. Change the MetricSampleAggregator data layout. We store the metric values of different windows into an circular array to save memroy.\r\n2. Allow users to define there own metric type.\r\n3. Embed the completeness check into the aggregator itself.","created_at":"2017-10-19T18:48:45Z","updated_at":"2018-02-18T05:46:33Z","closed_at":null,"merged_at":null,"merge_commit_sha":"7e110638a29a04b219e393eea4d42c11495a272a","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/linkedin/cruise-control/pulls/64/commits","review_comments_url":"https://api.github.com/repos/linkedin/cruise-control/pulls/64/comments","review_comment_url":"https://api.github.com/repos/linkedin/cruise-control/pulls/comments{/number}","comments_url":"https://api.github.com/repos/linkedin/cruise-control/issues/64/comments","statuses_url":"https://api.github.com/repos/linkedin/cruise-control/statuses/b5f6ba84b5bcbf4a9673db85391b86bf14bab9f4","head":{"label":"becketqin:refactor_MetricSampleAggregator_1","ref":"refactor_MetricSampleAggregator_1","sha":"b5f6ba84b5bcbf4a9673db85391b86bf14bab9f4","user":{"login":"becketqin","id":5778611,"avatar_url":"https://avatars3.githubusercontent.com/u/5778611?v=4","gravatar_id":"","url":"https://api.github.com/users/becketqin","html_url":"https://github.com/becketqin","followers_url":"https://api.github.com/users/becketqin/followers","following_url":"https://api.github.com/users/becketqin/following{/other_user}","gists_url":"https://api.github.com/users/becketqin/gists{/gist_id}","starred_url":"https://api.github.com/users/becketqin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/becketqin/subscriptions","organizations_url":"https://api.github.com/users/becketqin/orgs","repos_url":"https://api.github.com/users/becketqin/repos","events_url":"https://api.github.com/users/becketqin/events{/privacy}","received_events_url":"https://api.github.com/users/becketqin/received_events","type":"User","site_admin":false},"repo":{"id":101806820,"name":"cruise-control","full_name":"becketqin/cruise-control","owner":{"login":"becketqin","id":5778611,"avatar_url":"https://avatars3.githubusercontent.com/u/5778611?v=4","gravatar_id":"","url":"https://api.github.com/users/becketqin","html_url":"https://github.com/becketqin","followers_url":"https://api.github.com/users/becketqin/followers","following_url":"https://api.github.com/users/becketqin/following{/other_user}","gists_url":"https://api.github.com/users/becketqin/gists{/gist_id}","starred_url":"https://api.github.com/users/becketqin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/becketqin/subscriptions","organizations_url":"https://api.github.com/users/becketqin/orgs","repos_url":"https://api.github.com/users/becketqin/repos","events_url":"https://api.github.com/users/becketqin/events{/privacy}","received_events_url":"https://api.github.com/users/becketqin/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/becketqin/cruise-control","description":"Cruise-control is the first of its kind to fully automate the dynamic workload rebalance and self-healing of a kafka cluster. It provides great value to Kafka users by simplifying the operation of Kafka clusters.","fork":true,"url":"https://api.github.com/repos/becketqin/cruise-control","forks_url":"https://api.github.com/repos/becketqin/cruise-control/forks","keys_url":"https://api.github.com/repos/becketqin/cruise-control/keys{/key_id}","collaborators_url":"https://api.github.com/repos/becketqin/cruise-control/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/becketqin/cruise-control/teams","hooks_url":"https://api.github.com/repos/becketqin/cruise-control/hooks","issue_events_url":"https://api.github.com/repos/becketqin/cruise-control/issues/events{/number}","events_url":"https://api.github.com/repos/becketqin/cruise-control/events","assignees_url":"https://api.github.com/repos/becketqin/cruise-control/assignees{/user}","branches_url":"https://api.github.com/repos/becketqin/cruise-control/branches{/branch}","tags_url":"https://api.github.com/repos/becketqin/cruise-control/tags","blobs_url":"https://api.github.com/repos/becketqin/cruise-control/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/becketqin/cruise-control/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/becketqin/cruise-control/git/refs{/sha}","trees_url":"https://api.github.com/repos/becketqin/cruise-control/git/trees{/sha}","statuses_url":"https://api.github.com/repos/becketqin/cruise-control/statuses/{sha}","languages_url":"https://api.github.com/repos/becketqin/cruise-control/languages","stargazers_url":"https://api.github.com/repos/becketqin/cruise-control/stargazers","contributors_url":"https://api.github.com/repos/becketqin/cruise-control/contributors","subscribers_url":"https://api.github.com/repos/becketqin/cruise-control/subscribers","subscription_url":"https://api.github.com/repos/becketqin/cruise-control/subscription","commits_url":"https://api.github.com/repos/becketqin/cruise-control/commits{/sha}","git_commits_url":"https://api.github.com/repos/becketqin/cruise-control/git/commits{/sha}","comments_url":"https://api.github.com/repos/becketqin/cruise-control/comments{/number}","issue_comment_url":"https://api.github.com/repos/becketqin/cruise-control/issues/comments{/number}","contents_url":"https://api.github.com/repos/becketqin/cruise-control/contents/{+path}","compare_url":"https://api.github.com/repos/becketqin/cruise-control/compare/{base}...{head}","merges_url":"https://api.github.com/repos/becketqin/cruise-control/merges","archive_url":"https://api.github.com/repos/becketqin/cruise-control/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/becketqin/cruise-control/downloads","issues_url":"https://api.github.com/repos/becketqin/cruise-control/issues{/number}","pulls_url":"https://api.github.com/repos/becketqin/cruise-control/pulls{/number}","milestones_url":"https://api.github.com/repos/becketqin/cruise-control/milestones{/number}","notifications_url":"https://api.github.com/repos/becketqin/cruise-control/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/becketqin/cruise-control/labels{/name}","releases_url":"https://api.github.com/repos/becketqin/cruise-control/releases{/id}","deployments_url":"https://api.github.com/repos/becketqin/cruise-control/deployments","created_at":"2017-08-29T21:05:26Z","updated_at":"2017-08-29T21:05:28Z","pushed_at":"2018-02-17T20:43:34Z","git_url":"git://github.com/becketqin/cruise-control.git","ssh_url":"git@github.com:becketqin/cruise-control.git","clone_url":"https://github.com/becketqin/cruise-control.git","svn_url":"https://github.com/becketqin/cruise-control","homepage":"","size":1239,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"bsd-2-clause","name":"BSD 2-Clause \"Simplified\" License","spdx_id":"BSD-2-Clause","url":"https://api.github.com/licenses/bsd-2-clause"},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"linkedin:master","ref":"master","sha":"2e07e7240cc6c4469e203bc2b4591b922769ee41","user":{"login":"linkedin","id":357098,"avatar_url":"https://avatars3.githubusercontent.com/u/357098?v=4","gravatar_id":"","url":"https://api.github.com/users/linkedin","html_url":"https://github.com/linkedin","followers_url":"https://api.github.com/users/linkedin/followers","following_url":"https://api.github.com/users/linkedin/following{/other_user}","gists_url":"https://api.github.com/users/linkedin/gists{/gist_id}","starred_url":"https://api.github.com/users/linkedin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/linkedin/subscriptions","organizations_url":"https://api.github.com/users/linkedin/orgs","repos_url":"https://api.github.com/users/linkedin/repos","events_url":"https://api.github.com/users/linkedin/events{/privacy}","received_events_url":"https://api.github.com/users/linkedin/received_events","type":"Organization","site_admin":false},"repo":{"id":100402355,"name":"cruise-control","full_name":"linkedin/cruise-control","owner":{"login":"linkedin","id":357098,"avatar_url":"https://avatars3.githubusercontent.com/u/357098?v=4","gravatar_id":"","url":"https://api.github.com/users/linkedin","html_url":"https://github.com/linkedin","followers_url":"https://api.github.com/users/linkedin/followers","following_url":"https://api.github.com/users/linkedin/following{/other_user}","gists_url":"https://api.github.com/users/linkedin/gists{/gist_id}","starred_url":"https://api.github.com/users/linkedin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/linkedin/subscriptions","organizations_url":"https://api.github.com/users/linkedin/orgs","repos_url":"https://api.github.com/users/linkedin/repos","events_url":"https://api.github.com/users/linkedin/events{/privacy}","received_events_url":"https://api.github.com/users/linkedin/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/linkedin/cruise-control","description":"Cruise-control is the first of its kind to fully automate the dynamic workload rebalance and self-healing of a kafka cluster. It provides great value to Kafka users by simplifying the operation of Kafka clusters.","fork":false,"url":"https://api.github.com/repos/linkedin/cruise-control","forks_url":"https://api.github.com/repos/linkedin/cruise-control/forks","keys_url":"https://api.github.com/repos/linkedin/cruise-control/keys{/key_id}","collaborators_url":"https://api.github.com/repos/linkedin/cruise-control/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/linkedin/cruise-control/teams","hooks_url":"https://api.github.com/repos/linkedin/cruise-control/hooks","issue_events_url":"https://api.github.com/repos/linkedin/cruise-control/issues/events{/number}","events_url":"https://api.github.com/repos/linkedin/cruise-control/events","assignees_url":"https://api.github.com/repos/linkedin/cruise-control/assignees{/user}","branches_url":"https://api.github.com/repos/linkedin/cruise-control/branches{/branch}","tags_url":"https://api.github.com/repos/linkedin/cruise-control/tags","blobs_url":"https://api.github.com/repos/linkedin/cruise-control/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/linkedin/cruise-control/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/linkedin/cruise-control/git/refs{/sha}","trees_url":"https://api.github.com/repos/linkedin/cruise-control/git/trees{/sha}","statuses_url":"https://api.github.com/repos/linkedin/cruise-control/statuses/{sha}","languages_url":"https://api.github.com/repos/linkedin/cruise-control/languages","stargazers_url":"https://api.github.com/repos/linkedin/cruise-control/stargazers","contributors_url":"https://api.github.com/repos/linkedin/cruise-control/contributors","subscribers_url":"https://api.github.com/repos/linkedin/cruise-control/subscribers","subscription_url":"https://api.github.com/repos/linkedin/cruise-control/subscription","commits_url":"https://api.github.com/repos/linkedin/cruise-control/commits{/sha}","git_commits_url":"https://api.github.com/repos/linkedin/cruise-control/git/commits{/sha}","comments_url":"https://api.github.com/repos/linkedin/cruise-control/comments{/number}","issue_comment_url":"https://api.github.com/repos/linkedin/cruise-control/issues/comments{/number}","contents_url":"https://api.github.com/repos/linkedin/cruise-control/contents/{+path}","compare_url":"https://api.github.com/repos/linkedin/cruise-control/compare/{base}...{head}","merges_url":"https://api.github.com/repos/linkedin/cruise-control/merges","archive_url":"https://api.github.com/repos/linkedin/cruise-control/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/linkedin/cruise-control/downloads","issues_url":"https://api.github.com/repos/linkedin/cruise-control/issues{/number}","pulls_url":"https://api.github.com/repos/linkedin/cruise-control/pulls{/number}","milestones_url":"https://api.github.com/repos/linkedin/cruise-control/milestones{/number}","notifications_url":"https://api.github.com/repos/linkedin/cruise-control/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/linkedin/cruise-control/labels{/name}","releases_url":"https://api.github.com/repos/linkedin/cruise-control/releases{/id}","deployments_url":"https://api.github.com/repos/linkedin/cruise-control/deployments","created_at":"2017-08-15T17:32:10Z","updated_at":"2018-02-15T12:44:10Z","pushed_at":"2018-02-14T19:37:41Z","git_url":"git://github.com/linkedin/cruise-control.git","ssh_url":"git@github.com:linkedin/cruise-control.git","clone_url":"https://github.com/linkedin/cruise-control.git","svn_url":"https://github.com/linkedin/cruise-control","homepage":"","size":1036,"stargazers_count":657,"watchers_count":657,"language":"Java","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":128,"mirror_url":null,"archived":false,"open_issues_count":25,"license":{"key":"bsd-2-clause","name":"BSD 2-Clause \"Simplified\" License","spdx_id":"BSD-2-Clause","url":"https://api.github.com/licenses/bsd-2-clause"},"forks":128,"open_issues":25,"watchers":657,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/linkedin/cruise-control/pulls/64"},"html":{"href":"https://github.com/linkedin/cruise-control/pull/64"},"issue":{"href":"https://api.github.com/repos/linkedin/cruise-control/issues/64"},"comments":{"href":"https://api.github.com/repos/linkedin/cruise-control/issues/64/comments"},"review_comments":{"href":"https://api.github.com/repos/linkedin/cruise-control/pulls/64/comments"},"review_comment":{"href":"https://api.github.com/repos/linkedin/cruise-control/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/linkedin/cruise-control/pulls/64/commits"},"statuses":{"href":"https://api.github.com/repos/linkedin/cruise-control/statuses/b5f6ba84b5bcbf4a9673db85391b86bf14bab9f4"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"becketqin"}}