static void
cockpit_internal_metrics_sample (CockpitSamples *samples,
                                 const gchar *metric,
                                 const gchar *instance,
                                 gint64 value)
{
  CockpitInternalMetrics *self = COCKPIT_INTERNAL_METRICS (samples);

  for (int i = 0; i < self->n_metrics; i++)
    {
      MetricInfo *info = &self->metrics[i];
      if (g_strcmp0 (metric, info->desc->name) != 0)
        continue;

      if (info->desc->instanced)
        {
          InstanceInfo *inst = g_hash_table_lookup (info->instances, instance);
          if (inst == NULL)
            {
              g_debug ("%s + %s", metric, instance);
              inst = g_new0 (InstanceInfo, 1);
              g_hash_table_insert (info->instances, g_strdup (instance), inst);
              self->need_meta = TRUE;
            }
          inst->seen = TRUE;
          inst->value = value;
        }
      else
        info->value = value;
    }
}
static void
cockpit_internal_metrics_dispose (GObject *object)
{
#if 0
  CockpitInternalMetrics *self = COCKPIT_INTERNAL_METRICS (object);
#endif
  G_OBJECT_CLASS (cockpit_internal_metrics_parent_class)->dispose (object);
}
static void
cockpit_internal_metrics_finalize (GObject *object)
{
  CockpitInternalMetrics *self = COCKPIT_INTERNAL_METRICS (object);

  g_free (self->instances);
  g_free (self->omit_instances);
  g_free (self->metrics);

  G_OBJECT_CLASS (cockpit_internal_metrics_parent_class)->finalize (object);
}
static void
cockpit_internal_metrics_finalize (GObject *object)
{
  CockpitInternalMetrics *self = COCKPIT_INTERNAL_METRICS (object);

  g_free (self->instances);
  g_free (self->omit_instances);

  for (int i = 0; i < self->n_metrics; i++)
    {
      MetricInfo *info = &self->metrics[i];
      if (info->instances)
        g_hash_table_unref (info->instances);
    }

  g_free (self->metrics);

  G_OBJECT_CLASS (cockpit_internal_metrics_parent_class)->finalize (object);
}
static void
cockpit_internal_metrics_prepare (CockpitChannel *channel)
{
  CockpitInternalMetrics *self = COCKPIT_INTERNAL_METRICS (channel);
  JsonObject *options;
  JsonArray *metrics;
  int i;

  COCKPIT_CHANNEL_CLASS (cockpit_internal_metrics_parent_class)->prepare (channel);

  options = cockpit_channel_get_options (channel);

  /* "instances" option */
  if (!cockpit_json_get_strv (options, "instances", NULL, (gchar ***)&self->instances))
    {
      cockpit_channel_fail (channel, "protocol-error",
                            "invalid \"instances\" option (not an array of strings)");
      return;
    }

  /* "omit-instances" option */
  if (!cockpit_json_get_strv (options, "omit-instances", NULL, (gchar ***)&self->omit_instances))
    {
      cockpit_channel_fail (channel, "protocol-error",
                            "invalid \"omit-instances\" option (not an array of strings)");
      return;
    }

  /* "metrics" option */
  self->n_metrics = 0;
  if (!cockpit_json_get_array (options, "metrics", NULL, &metrics))
    {
      cockpit_channel_fail (channel, "protocol-error",
                            "invalid \"metrics\" option was specified (not an array)");
      return;
    }
  if (metrics)
    self->n_metrics = json_array_get_length (metrics);

  self->metrics = g_new0 (MetricInfo, self->n_metrics);
  for (i = 0; i < self->n_metrics; i++)
    {
      MetricInfo *info = &self->metrics[i];
      if (!convert_metric_description (self, json_array_get_element (metrics, i), info, i))
        return;
      if (!info->desc)
        {
          cockpit_channel_close (channel, "not-supported");
          return;
        }
    }

  /* "interval" option */
  if (!cockpit_json_get_int (options, "interval", 1000, &self->interval))
    {
      cockpit_channel_fail (channel, "protocol-error", "invalid \"interval\" option");
      return;
    }
  else if (self->interval <= 0 || self->interval > G_MAXINT)
    {
      cockpit_channel_fail (channel, "protocol-error",
                            "invalid \"interval\" value: %" G_GINT64_FORMAT, self->interval);
      return;
    }

  self->need_meta = TRUE;

  cockpit_metrics_metronome (COCKPIT_METRICS (self), self->interval);
  cockpit_channel_ready (channel, NULL);
}