int main(int argc, char** argv) { const char* command = "list"; bool verbose = false; int c; while ((c = getopt_long(argc, argv, "+hv", kLongOptions, NULL)) != -1) { switch (c) { case 0: break; case 'h': usage(0); break; case 'v': verbose = true; break; default: usage(1); break; } } if (argc - optind >= 1) command = argv[optind]; if (strcmp(command, "list") == 0) { list_jobs(verbose); } else if (strcmp(command, "list-targets") == 0) { list_targets(verbose); } else if (strcmp(command, "log") == 0) { get_log(argc - optind, &argv[optind]); } else if (argc == optind + 1) { // For convenience (the "info" command can be omitted) get_info(command); } else { // All commands that need a name following const char* name = argv[argc - 1]; if (strcmp(command, "info") == 0) { get_info(name); } else if (strcmp(command, "start") == 0) { start_job(name); } else if (strcmp(command, "stop") == 0) { stop_job(name); } else if (strcmp(command, "restart") == 0) { restart_job(name); } else if (strcmp(command, "enable") == 0) { enable_job(name, true); } else if (strcmp(command, "disable") == 0) { enable_job(name, false); } else { fprintf(stderr, "%s: Unknown command \"%s\".\n", kProgramName, command); } } return 0; }
/** * gstreamill_job_stop: * @name: (in): job name to be stoped * * Returns: plain text. */ gchar * gstreamill_job_stop (Gstreamill *gstreamill, gchar *name) { Job *job; job = get_job (gstreamill, name); if (job != NULL) { stop_job (job, SIGTERM); return g_strdup_printf ("{\n \"name\": \"%s\",\n \"result\": \"success\"\n}", name); } else { return g_strdup ("{\n \"result\": \"failure\",\n \"reason\": \"job not found\"\n}"); } }
/** * gstreamill_job_stop: * @name: (in): job name to be stoped * * Returns: plain text. */ gchar * gstreamill_job_stop (Gstreamill *gstreamill, gchar *name) { Job *job; job = get_job (gstreamill, name); if (job != NULL) { stop_job (job, SIGUSR2); return g_strdup ("ok"); } else { return g_strdup ("job not found"); } }
/** * gstreamill_stop: * @gstreamill: (in): gstreamill to be stop * * stop gstreamill, stop job first before stop gstreamill. * * Returns: none */ void gstreamill_stop (Gstreamill *gstreamill) { Job *job; GSList *list; gstreamill->stop = TRUE; g_mutex_lock (&(gstreamill->job_list_mutex)); list = gstreamill->job_list; while (list != NULL) { job = list->data; stop_job (job, SIGUSR2); list = list->next; } g_mutex_unlock (&(gstreamill->job_list_mutex)); return; }
static void source_check (Gstreamill *gstreamill, Job *job) { gint i; GstClockTimeDiff time_diff; GstClockTime now; /* log source timestamp. */ for (i = 0; i < job->output->source.stream_count; i++) { GST_INFO ("%s.source.%s timestamp %" GST_TIME_FORMAT, job->name, job->output->source.streams[i].name, GST_TIME_ARGS (job->output->source.streams[i].current_timestamp)); } /* non live job, don't check heartbeat */ if (!job->is_live) { return; } /* source heartbeat check */ for (i = 0; i < job->output->source.stream_count; i++) { /* check video and audio */ if (!g_str_has_prefix (job->output->source.streams[i].name, "video") && !g_str_has_prefix (job->output->source.streams[i].name, "audio")) { continue; } now = gst_clock_get_time (gstreamill->system_clock); time_diff = GST_CLOCK_DIFF (job->output->source.streams[i].last_heartbeat, now); if ((time_diff > HEARTBEAT_THRESHHOLD) && gstreamill->daemon) { GST_ERROR ("%s.source.%s heart beat error %lu, restart job.", job->name, job->output->source.streams[i].name, time_diff); /* restart job. */ stop_job (job, SIGKILL); return; } else { GST_INFO ("%s.source.%s heartbeat %" GST_TIME_FORMAT, job->name, job->output->source.streams[i].name, GST_TIME_ARGS (job->output->source.streams[i].last_heartbeat)); } } }
/** * gstreamill_stop: * @gstreamill: (in): gstreamill to be stop * * stop gstreamill, stop job first before stop gstreamill. * * Returns: none */ void gstreamill_stop (Gstreamill *gstreamill) { Job *job; GSList *list; GST_WARNING ("Stop gstreamill ..."); gstreamill->stop = TRUE; g_mutex_lock (&(gstreamill->job_list_mutex)); list = gstreamill->job_list; while (list != NULL) { job = list->data; if (gstreamill->daemon) { stop_job (job, SIGTERM); } else { *(job->output->state) = JOB_STATE_NULL; } list = list->next; } g_mutex_unlock (&(gstreamill->job_list_mutex)); return; }
static void sync_check (Gstreamill *gstreamill, Job *job) { gint j; GstClockTimeDiff time_diff; GstClockTime min, max; min = GST_CLOCK_TIME_NONE; max = 0; for (j = 0; j < job->output->source.stream_count; j++) { if (!g_str_has_prefix (job->output->source.streams[j].name, "video") && !g_str_has_prefix (job->output->source.streams[j].name, "audio")) { continue; } if (min > job->output->source.streams[j].current_timestamp) { min = job->output->source.streams[j].current_timestamp; } if (max < job->output->source.streams[j].current_timestamp) { max = job->output->source.streams[j].current_timestamp; } } time_diff = GST_CLOCK_DIFF (min, max); if ((time_diff > SYNC_THRESHHOLD) && gstreamill->daemon){ GST_ERROR ("%s sync error %lu", job->name, time_diff); job->output->source.sync_error_times += 1; if (job->output->source.sync_error_times == 3) { GST_ERROR ("sync error times %ld, restart %s", job->output->source.sync_error_times, job->name); /* restart job. */ stop_job (job, SIGKILL); return; } } else { job->output->source.sync_error_times = 0; } }
static void job_check_func (gpointer data, gpointer user_data) { Job *job = (Job *)data; Gstreamill *gstreamill = (Gstreamill *)user_data; if (gstreamill->stop) { GST_ERROR ("waitting %s stopped", job->name); return; } /* stat report. */ if (gstreamill->daemon && (job->worker_pid != 0)) { job_stat_update (job); GST_INFO ("Job %s's average cpu: %d%%, cpu: %d%%, rss: %lu", job->name, job->cpu_average, job->cpu_current, job->memory); } if (*(job->output->state) != JOB_STATE_PLAYING) { return; } source_check (gstreamill, job); encoders_check (gstreamill, job); if (job->is_live) { sync_check (gstreamill, job); } /* check non live job eos */ if (!job->is_live) { gint i; gboolean eos = TRUE; for (i = 0; i < job->output->encoder_count; i++) { if (!(*(job->output->encoders[i].eos))) { eos = FALSE; break; } else { /* add #EXT-X-ENDLIST to playlist if output is m3u8 */ gchar *location, *property, *playlist1, *playlist2; property = g_strdup_printf ("encoder.%d.elements.hlssink.property.playlist-location", i); location = jobdesc_element_property_value (job->description, property); g_free (property); if (location != NULL) { g_file_get_contents (location, &playlist1, NULL, NULL); playlist2 = g_strdup_printf ("%s#EXT-X-ENDLIST\n", playlist1); g_file_set_contents (location, playlist2, strlen(playlist2), NULL); g_free (playlist1); g_free (playlist2); g_free (location); } } } if (eos) { stop_job (job, SIGTERM); job->eos = TRUE; } } }
static void encoders_check (Gstreamill *gstreamill, Job *job) { gint j, k; GstClockTimeDiff time_diff; GstClockTime now; /* log encoder current timestamp. */ for (j = 0; j < job->output->encoder_count; j++) { for (k = 0; k < job->output->encoders[j].stream_count; k++) { GST_INFO ("%s.%s timestamp %" GST_TIME_FORMAT, job->output->encoders[j].name, job->output->encoders[j].streams[k].name, GST_TIME_ARGS (job->output->encoders[j].streams[k].current_timestamp)); } } /* non live job, don't check heartbeat */ if (!job->is_live) { return; } /* encoder heartbeat check */ for (j = 0; j < job->output->encoder_count; j++) { for (k = 0; k < job->output->encoders[j].stream_count; k++) { if (!g_str_has_prefix (job->output->encoders[j].streams[k].name, "video") && !g_str_has_prefix (job->output->encoders[j].streams[k].name, "audio")) { continue; } now = gst_clock_get_time (gstreamill->system_clock); time_diff = GST_CLOCK_DIFF (job->output->encoders[j].streams[k].last_heartbeat, now); if ((time_diff > HEARTBEAT_THRESHHOLD) && gstreamill->daemon) { GST_ERROR ("%s.%s heartbeat error %lu, restart", job->output->encoders[j].name, job->output->encoders[j].streams[k].name, time_diff); /* restart job. */ stop_job (job, SIGKILL); return; } else { GST_INFO ("%s.%s heartbeat %" GST_TIME_FORMAT, job->output->encoders[j].name, job->output->encoders[j].streams[k].name, GST_TIME_ARGS (job->output->encoders[j].streams[k].last_heartbeat)); } } } /* encoder job->output heartbeat check. */ for (j = 0; j < job->output->encoder_count; j++) { now = gst_clock_get_time (gstreamill->system_clock); time_diff = GST_CLOCK_DIFF (*(job->output->encoders[j].heartbeat), now); if ((time_diff > ENCODER_OUTPUT_HEARTBEAT_THRESHHOLD) && gstreamill->daemon) { GST_ERROR ("%s job->output heart beat error %lu, restart", job->output->encoders[j].name, time_diff); /* restart job. */ stop_job (job, SIGKILL); return; } else { GST_INFO ("%s job->output heartbeat %" GST_TIME_FORMAT, job->output->encoders[j].name, GST_TIME_ARGS (*(job->output->encoders[j].heartbeat))); } } }
static void restart_job(const char* name) { stop_job(name); start_job(name); }
static gsize request_gstreamill_admin (HTTPMgmt *httpmgmt, RequestData *request_data, gchar **buf) { gchar *path, *http_header, *p; gsize buf_size; GError *err = NULL; path = NULL; if (g_strcmp0 (request_data->uri, "/admin/") == 0) { path = g_strdup_printf ("%s/gstreamill/admin/index.html", DATADIR); } else if (g_str_has_prefix (request_data->uri, "/admin/start")) { p = start_job (httpmgmt, request_data); *buf = g_strdup_printf (http_200, PACKAGE_NAME, PACKAGE_VERSION, "application/json", strlen (p), NO_CACHE, p); g_free (p); } else if (g_str_has_prefix (request_data->uri, "/admin/stop")) { p = stop_job (httpmgmt, request_data); *buf = g_strdup_printf (http_200, PACKAGE_NAME, PACKAGE_VERSION, "application/json", strlen (p), NO_CACHE, p); g_free (p); } else if (g_strcmp0 (request_data->uri, "/admin/networkinterfaces") == 0) { p = network_interfaces (); *buf = g_strdup_printf (http_200, PACKAGE_NAME, PACKAGE_VERSION, "application/json", strlen (p), NO_CACHE, p); g_free (p); } else if (g_strcmp0 (request_data->uri, "/admin/setnetworkinterfaces") == 0) { p = set_network_interfaces (request_data); *buf = g_strdup_printf (http_200, PACKAGE_NAME, PACKAGE_VERSION, "application/json", strlen (p), NO_CACHE, p); g_free (p); } else if (g_strcmp0 (request_data->uri, "/admin/networkdevices") == 0) { p = network_devices (); *buf = g_strdup_printf (http_200, PACKAGE_NAME, PACKAGE_VERSION, "application/json", strlen (p), NO_CACHE, p); g_free (p); } else if (g_strcmp0 (request_data->uri, "/admin/audiodevices") == 0) { p = list_files ("/dev/snd/pcmC*c", NULL); *buf = g_strdup_printf (http_200, PACKAGE_NAME, PACKAGE_VERSION, "application/json", strlen (p), NO_CACHE, p); g_free (p); } else if (g_strcmp0 (request_data->uri, "/admin/videodevices") == 0) { p = list_files ("/dev/video*", NULL); *buf = g_strdup_printf (http_200, PACKAGE_NAME, PACKAGE_VERSION, "application/json", strlen (p), NO_CACHE, p); g_free (p); } else if (g_strcmp0 (request_data->uri, "/admin/getconf") == 0) { p = get_conf (); *buf = g_strdup_printf (http_200, PACKAGE_NAME, PACKAGE_VERSION, "application/json", strlen (p), NO_CACHE, p); g_free (p); } else if ((request_data->method == HTTP_POST) && (g_strcmp0 (request_data->uri, "/admin/putconf") == 0)) { p = put_conf (request_data); *buf = g_strdup_printf (http_200, PACKAGE_NAME, PACKAGE_VERSION, "application/json", strlen (p), NO_CACHE, p); g_free (p); } else if (g_strcmp0 (request_data->uri, "/admin/listlivejob") == 0) { p = list_files ("/etc/gstreamill.d/*.job", "/etc/gstreamill.d/%[^.].job"); *buf = g_strdup_printf (http_200, PACKAGE_NAME, PACKAGE_VERSION, "application/json", strlen (p), NO_CACHE, p); g_free (p); } else if (g_strcmp0 (request_data->uri, "/admin/listrunningjob") == 0) { p = gstreamill_list_running_job (httpmgmt->gstreamill); *buf = g_strdup_printf (http_200, PACKAGE_NAME, PACKAGE_VERSION, "application/json", strlen (p), NO_CACHE, p); g_free (p); } else if (g_str_has_prefix (request_data->uri, "/admin/getjob/")) { p = get_job (request_data->uri); if (p != NULL) { *buf = g_strdup_printf (http_200, PACKAGE_NAME, PACKAGE_VERSION, "application/json", strlen (p), NO_CACHE, p); g_free (p); } else { *buf = g_strdup_printf (http_404, PACKAGE_NAME, PACKAGE_VERSION); } } else if (g_str_has_prefix (request_data->uri, "/admin/rmjob/")) { p = rm_job (request_data->uri); *buf = g_strdup_printf (http_200, PACKAGE_NAME, PACKAGE_VERSION, "application/json", strlen (p), NO_CACHE, p); g_free (p); } else if ((request_data->method == HTTP_POST) && (g_str_has_prefix (request_data->uri, "/admin/putjob/"))) { p = put_job (request_data); *buf = g_strdup_printf (http_200, PACKAGE_NAME, PACKAGE_VERSION, "application/json", strlen (p), NO_CACHE, p); g_free (p); } else if (g_str_has_prefix (request_data->uri, "/admin/jobmanage.html")) { if (g_str_has_prefix (request_data->parameters, "name=")) { path = g_strdup_printf ("%s/gstreamill/admin/jobmanage.html", DATADIR); } else { *buf = g_strdup_printf (http_400, PACKAGE_NAME, PACKAGE_VERSION); } } else { /* static content, prepare file path */ path = g_strdup_printf ("%s/gstreamill%s", DATADIR, request_data->uri); } if (path == NULL) { /* not static content */ buf_size = strlen (*buf); } else if (!g_file_get_contents (path, buf, &buf_size, &err)) { GST_ERROR ("read file %s failure: %s", p, err->message); *buf = g_strdup_printf (http_404, PACKAGE_NAME, PACKAGE_VERSION); buf_size = strlen (*buf); g_error_free (err); } else { /* jobmanage.html? process name parameter */ if (g_str_has_suffix (path, "jobmanage.html")) { gchar name[32], *temp_buf; sscanf (request_data->parameters, "name=%s", name); temp_buf = g_strdup_printf (*buf, name); g_free (*buf); *buf = temp_buf; } /* html file? add top and bottom */ if (g_str_has_suffix (path, ".html")) { gchar *body; body = *buf; *buf = add_header_footer (body); g_free (body); buf_size = strlen (*buf); } http_header = gen_http_header (path, buf_size); p = g_malloc (buf_size + strlen (http_header)); memcpy (p, http_header, strlen (http_header)); memcpy (p + strlen (http_header), *buf, buf_size); g_free (*buf); *buf = p; buf_size += strlen (http_header); g_free (http_header); } if (path != NULL) { g_free (path); } return buf_size; }