protected void connectEnrichers(Duration windowPeriod) {
    JavaAppUtils.connectJavaAppServerPolicies(this);

    addEnricher(
        TimeWeightedDeltaEnricher.<Long>getPerSecondDeltaEnricher(
            this, READ_COMPLETED, READS_PER_SECOND_LAST));
    addEnricher(
        TimeWeightedDeltaEnricher.<Long>getPerSecondDeltaEnricher(
            this, WRITE_COMPLETED, WRITES_PER_SECOND_LAST));

    if (windowPeriod != null) {
      addEnricher(
          new RollingTimeWindowMeanEnricher<Long>(
              this, THRIFT_PORT_LATENCY, THRIFT_PORT_LATENCY_IN_WINDOW, windowPeriod));
      addEnricher(
          new RollingTimeWindowMeanEnricher<Double>(
              this, READS_PER_SECOND_LAST, READS_PER_SECOND_IN_WINDOW, windowPeriod));
      addEnricher(
          new RollingTimeWindowMeanEnricher<Double>(
              this, WRITES_PER_SECOND_LAST, WRITES_PER_SECOND_IN_WINDOW, windowPeriod));
    }
  }
  @SuppressWarnings({"unchecked", "rawtypes"})
  @Override
  protected void connectSensors() {
    // "cassandra" isn't really a protocol, but okay for now
    setAttribute(
        DATASTORE_URL, "cassandra://" + getAttribute(HOSTNAME) + ":" + getAttribute(THRIFT_PORT));

    super.connectSensors();

    jmxHelper = new JmxHelper(this);
    boolean retrieveUsageMetrics = getConfig(RETRIEVE_USAGE_METRICS);

    jmxFeed =
        JmxFeed.builder()
            .entity(this)
            .period(3000, TimeUnit.MILLISECONDS)
            .helper(jmxHelper)
            .pollAttribute(
                new JmxAttributePollConfig<Boolean>(SERVICE_UP_JMX)
                    .objectName(storageServiceMBean)
                    .attributeName("Initialized")
                    .onSuccess(Functions.forPredicate(Predicates.notNull()))
                    .onException(Functions.constant(false))
                    .suppressDuplicates(true))
            .pollAttribute(
                new JmxAttributePollConfig<Set<BigInteger>>(TOKENS)
                    .objectName(storageServiceMBean)
                    .attributeName("TokenToEndpointMap")
                    .onSuccess(
                        new Function<Object, Set<BigInteger>>() {
                          @Override
                          public Set<BigInteger> apply(@Nullable Object arg) {
                            Map input = (Map) arg;
                            if (input == null || input.isEmpty()) return null;
                            // FIXME does not work on aws-ec2, uses RFC1918 address
                            Predicate<String> self =
                                Predicates.in(
                                    ImmutableList.of(
                                        getAttribute(HOSTNAME),
                                        getAttribute(ADDRESS),
                                        getAttribute(SUBNET_ADDRESS),
                                        getAttribute(SUBNET_HOSTNAME)));
                            Set<String> tokens = Maps.filterValues(input, self).keySet();
                            Set<BigInteger> result = Sets.newLinkedHashSet();
                            for (String token : tokens) {
                              result.add(new BigInteger(token));
                            }
                            return result;
                          }
                        })
                    .onException(Functions.<Set<BigInteger>>constant(null))
                    .suppressDuplicates(true))
            .pollAttribute(
                new JmxAttributePollConfig<BigInteger>(TOKEN)
                    .objectName(storageServiceMBean)
                    .attributeName("TokenToEndpointMap")
                    .onSuccess(
                        new Function<Object, BigInteger>() {
                          @Override
                          public BigInteger apply(@Nullable Object arg) {
                            Map input = (Map) arg;
                            // TODO remove duplication from setting TOKENS
                            if (input == null || input.isEmpty()) return null;
                            // FIXME does not work on aws-ec2, uses RFC1918 address
                            Predicate<String> self =
                                Predicates.in(
                                    ImmutableList.of(
                                        getAttribute(HOSTNAME),
                                        getAttribute(ADDRESS),
                                        getAttribute(SUBNET_ADDRESS),
                                        getAttribute(SUBNET_HOSTNAME)));
                            Set<String> tokens = Maps.filterValues(input, self).keySet();
                            String token = Iterables.getFirst(tokens, null);
                            return (token != null) ? new BigInteger(token) : null;
                          }
                        })
                    .onException(Functions.<BigInteger>constant(null))
                    .suppressDuplicates(true))
            .pollOperation(
                new JmxOperationPollConfig<String>(DATACENTER_NAME)
                    .period(60, TimeUnit.SECONDS)
                    .objectName(snitchMBean)
                    .operationName("getDatacenter")
                    .operationParams(ImmutableList.of(getBroadcastAddress()))
                    .onException(Functions.<String>constant(null))
                    .suppressDuplicates(true))
            .pollOperation(
                new JmxOperationPollConfig<String>(RACK_NAME)
                    .period(60, TimeUnit.SECONDS)
                    .objectName(snitchMBean)
                    .operationName("getRack")
                    .operationParams(ImmutableList.of(getBroadcastAddress()))
                    .onException(Functions.<String>constant(null))
                    .suppressDuplicates(true))
            .pollAttribute(
                new JmxAttributePollConfig<Integer>(PEERS)
                    .objectName(storageServiceMBean)
                    .attributeName("TokenToEndpointMap")
                    .onSuccess(
                        new Function<Object, Integer>() {
                          @Override
                          public Integer apply(@Nullable Object arg) {
                            Map input = (Map) arg;
                            if (input == null || input.isEmpty()) return 0;
                            return input.size();
                          }
                        })
                    .onException(Functions.constant(-1)))
            .pollAttribute(
                new JmxAttributePollConfig<Integer>(LIVE_NODE_COUNT)
                    .objectName(storageServiceMBean)
                    .attributeName("LiveNodes")
                    .onSuccess(
                        new Function<Object, Integer>() {
                          @Override
                          public Integer apply(@Nullable Object arg) {
                            List input = (List) arg;
                            if (input == null || input.isEmpty()) return 0;
                            return input.size();
                          }
                        })
                    .onException(Functions.constant(-1)))
            .pollAttribute(
                new JmxAttributePollConfig<Integer>(READ_ACTIVE)
                    .objectName(readStageMBean)
                    .attributeName("ActiveCount")
                    .onException(Functions.constant((Integer) null))
                    .enabled(retrieveUsageMetrics))
            .pollAttribute(
                new JmxAttributePollConfig<Long>(READ_PENDING)
                    .objectName(readStageMBean)
                    .attributeName("PendingTasks")
                    .onException(Functions.constant((Long) null))
                    .enabled(retrieveUsageMetrics))
            .pollAttribute(
                new JmxAttributePollConfig<Long>(READ_COMPLETED)
                    .objectName(readStageMBean)
                    .attributeName("CompletedTasks")
                    .onException(Functions.constant((Long) null))
                    .enabled(retrieveUsageMetrics))
            .pollAttribute(
                new JmxAttributePollConfig<Integer>(WRITE_ACTIVE)
                    .objectName(mutationStageMBean)
                    .attributeName("ActiveCount")
                    .onException(Functions.constant((Integer) null))
                    .enabled(retrieveUsageMetrics))
            .pollAttribute(
                new JmxAttributePollConfig<Long>(WRITE_PENDING)
                    .objectName(mutationStageMBean)
                    .attributeName("PendingTasks")
                    .onException(Functions.constant((Long) null))
                    .enabled(retrieveUsageMetrics))
            .pollAttribute(
                new JmxAttributePollConfig<Long>(WRITE_COMPLETED)
                    .objectName(mutationStageMBean)
                    .attributeName("CompletedTasks")
                    .onException(Functions.constant((Long) null))
                    .enabled(retrieveUsageMetrics))
            .build();

    functionFeed =
        FunctionFeed.builder()
            .entity(this)
            .period(3000, TimeUnit.MILLISECONDS)
            .poll(
                new FunctionPollConfig<Long, Long>(THRIFT_PORT_LATENCY)
                    .onException(Functions.constant((Long) null))
                    .callable(
                        new Callable<Long>() {
                          public Long call() {
                            try {
                              long start = System.currentTimeMillis();
                              Socket s =
                                  new Socket(getAttribute(Attributes.HOSTNAME), getThriftPort());
                              s.close();
                              long latency = System.currentTimeMillis() - start;
                              computeServiceUp();
                              return latency;
                            } catch (Exception e) {
                              if (log.isDebugEnabled())
                                log.debug("Cassandra thrift port poll failure: " + e);
                              setAttribute(SERVICE_UP, false);
                              return null;
                            }
                          }

                          public void computeServiceUp() {
                            // this will wait an additional poll period after thrift port is up,
                            // as the caller will not have set yet, but that will help ensure it is
                            // really healthy!
                            setAttribute(
                                SERVICE_UP,
                                getAttribute(THRIFT_PORT_LATENCY) != null
                                    && getAttribute(THRIFT_PORT_LATENCY) >= 0
                                    && Boolean.TRUE.equals(getAttribute(SERVICE_UP_JMX)));
                          }
                        })
                    .enabled(retrieveUsageMetrics))
            .build();

    jmxMxBeanFeed = JavaAppUtils.connectMXBeanSensors(this);
  }