blob: c0540762698fc3c87eaa7fc2443eb39a3ad16600 [file] [log] [blame]
/**
* Copyright (C) ARM Limited 2010-2013. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <sys/prctl.h>
#include "Logging.h"
#include "CapturedXML.h"
#include "SessionData.h"
#include "Child.h"
#include "LocalCapture.h"
#include "Collector.h"
#include "Sender.h"
#include "OlyUtility.h"
#include "StreamlineSetup.h"
#include "ConfigurationXML.h"
#include "Driver.h"
#include "Fifo.h"
#include "Buffer.h"
#define NS_PER_S ((uint64_t)1000000000)
#define NS_PER_US 1000
static sem_t haltPipeline, senderThreadStarted, startProfile, senderSem; // Shared by Child and spawned threads
static Fifo* collectorFifo = NULL; // Shared by Child.cpp and spawned threads
static Buffer* buffer = NULL;
static Sender* sender = NULL; // Shared by Child.cpp and spawned threads
static Collector* collector = NULL;
Child* child = NULL; // shared by Child.cpp and main.cpp
extern void cleanUp();
void handleException() {
if (child && child->numExceptions++ > 0) {
// it is possible one of the below functions itself can cause an exception, thus allow only one exception
logg->logMessage("Received multiple exceptions, terminating the child");
exit(1);
}
fprintf(stderr, "%s", logg->getLastError());
if (child && child->socket) {
if (sender) {
// send the error, regardless of the command sent by Streamline
sender->writeData(logg->getLastError(), strlen(logg->getLastError()), RESPONSE_ERROR);
// cannot close the socket before Streamline issues the command, so wait for the command before exiting
if (gSessionData->mWaitingOnCommand) {
char discard;
child->socket->receiveNBytes(&discard, 1);
}
// Ensure all data is flushed
child->socket->shutdownConnection();
// this indirectly calls close socket which will ensure the data has been sent
delete sender;
}
}
if (gSessionData->mLocalCapture)
cleanUp();
exit(1);
}
// CTRL C Signal Handler for child process
static void child_handler(int signum) {
static bool beenHere = false;
if (beenHere == true) {
logg->logMessage("Gator is being forced to shut down.");
exit(1);
}
beenHere = true;
logg->logMessage("Gator is shutting down.");
if (signum == SIGALRM || !collector) {
exit(1);
} else {
child->endSession();
alarm(5); // Safety net in case endSession does not complete within 5 seconds
}
}
static void* durationThread(void* pVoid) {
prctl(PR_SET_NAME, (unsigned long)&"gatord-duration", 0, 0, 0);
sem_wait(&startProfile);
if (gSessionData->mSessionIsActive) {
// Time out after duration seconds
// Add a second for host-side filtering
sleep(gSessionData->mDuration + 1);
if (gSessionData->mSessionIsActive) {
logg->logMessage("Duration expired.");
child->endSession();
}
}
logg->logMessage("Exit duration thread");
return 0;
}
static void* stopThread(void* pVoid) {
OlySocket* socket = child->socket;
prctl(PR_SET_NAME, (unsigned long)&"gatord-stopper", 0, 0, 0);
while (gSessionData->mSessionIsActive) {
// This thread will stall until the APC_STOP or PING command is received over the socket or the socket is disconnected
unsigned char header[5];
const int result = socket->receiveNBytes((char*)&header, sizeof(header));
const char type = header[0];
const int length = (header[1] << 0) | (header[2] << 8) | (header[3] << 16) | (header[4] << 24);
if (result == -1) {
child->endSession();
} else if (result > 0) {
if ((type != COMMAND_APC_STOP) && (type != COMMAND_PING)) {
logg->logMessage("INVESTIGATE: Received unknown command type %d", type);
} else {
// verify a length of zero
if (length == 0) {
if (type == COMMAND_APC_STOP) {
logg->logMessage("Stop command received.");
child->endSession();
} else {
// Ping is used to make sure gator is alive and requires an ACK as the response
logg->logMessage("Ping command received.");
sender->writeData(NULL, 0, RESPONSE_ACK);
}
} else {
logg->logMessage("INVESTIGATE: Received stop command but with length = %d", length);
}
}
}
}
logg->logMessage("Exit stop thread");
return 0;
}
void* countersThread(void* pVoid) {
prctl(PR_SET_NAME, (unsigned long)&"gatord-counters", 0, 0, 0);
gSessionData->hwmon.start();
int64_t monotonic_started = 0;
while (monotonic_started <= 0) {
usleep(10);
if (Collector::readInt64Driver("/dev/gator/started", &monotonic_started) == -1) {
logg->logError(__FILE__, __LINE__, "Error reading gator driver start time");
handleException();
}
}
uint64_t next_time = 0;
while (gSessionData->mSessionIsActive) {
struct timespec ts;
#ifndef CLOCK_MONOTONIC_RAW
// Android doesn't have this defined but it was added in Linux 2.6.28
#define CLOCK_MONOTONIC_RAW 4
#endif
if (clock_gettime(CLOCK_MONOTONIC_RAW, &ts) != 0) {
logg->logError(__FILE__, __LINE__, "Failed to get uptime");
handleException();
}
const uint64_t curr_time = (NS_PER_S*ts.tv_sec + ts.tv_nsec) - monotonic_started;
// Sample ten times a second ignoring gSessionData->mSampleRate
next_time += NS_PER_S/10;//gSessionData->mSampleRate;
if (next_time < curr_time) {
logg->logMessage("Too slow, curr_time: %lli next_time: %lli", curr_time, next_time);
next_time = curr_time;
}
if (buffer->eventHeader(curr_time)) {
gSessionData->hwmon.read(buffer);
// Only check after writing all counters so that time and corresponding counters appear in the same frame
buffer->check(curr_time);
}
if (buffer->bytesAvailable() <= 0) {
logg->logMessage("One shot (counters)");
child->endSession();
}
usleep((next_time - curr_time)/NS_PER_US);
}
buffer->setDone();
return NULL;
}
static void* senderThread(void* pVoid) {
int length = 1;
char* data;
char end_sequence[] = {RESPONSE_APC_DATA, 0, 0, 0, 0};
sem_post(&senderThreadStarted);
prctl(PR_SET_NAME, (unsigned long)&"gatord-sender", 0, 0, 0);
sem_wait(&haltPipeline);
while (length > 0 || !buffer->isDone()) {
sem_wait(&senderSem);
data = collectorFifo->read(&length);
if (data != NULL) {
sender->writeData(data, length, RESPONSE_APC_DATA);
collectorFifo->release();
}
if (!buffer->isDone()) {
buffer->write(sender);
}
}
// write end-of-capture sequence
if (!gSessionData->mLocalCapture) {
sender->writeData(end_sequence, sizeof(end_sequence), RESPONSE_APC_DATA);
}
logg->logMessage("Exit sender thread");
return 0;
}
Child::Child() {
initialization();
gSessionData->mLocalCapture = true;
}
Child::Child(OlySocket* sock, int conn) {
initialization();
socket = sock;
mNumConnections = conn;
}
Child::~Child() {
}
void Child::initialization() {
// Set up different handlers for signals
gSessionData->mSessionIsActive = true;
signal(SIGINT, child_handler);
signal(SIGTERM, child_handler);
signal(SIGABRT, child_handler);
signal(SIGALRM, child_handler);
socket = NULL;
numExceptions = 0;
mNumConnections = 0;
// Initialize semaphores
sem_init(&senderThreadStarted, 0, 0);
sem_init(&startProfile, 0, 0);
sem_init(&senderSem, 0, 0);
}
void Child::endSession() {
gSessionData->mSessionIsActive = false;
collector->stop();
sem_post(&haltPipeline);
}
void Child::run() {
char* collectBuffer;
int bytesCollected = 0;
LocalCapture* localCapture = NULL;
pthread_t durationThreadID, stopThreadID, senderThreadID, countersThreadID;
prctl(PR_SET_NAME, (unsigned long)&"gatord-child", 0, 0, 0);
// Disable line wrapping when generating xml files; carriage returns and indentation to be added manually
mxmlSetWrapMargin(0);
// Instantiate the Sender - must be done first, after which error messages can be sent
sender = new Sender(socket);
if (mNumConnections > 1) {
logg->logError(__FILE__, __LINE__, "Session already in progress");
handleException();
}
// Populate gSessionData with the configuration
{ ConfigurationXML configuration; }
// Set up the driver; must be done after gSessionData->mPerfCounterType[] is populated
collector = new Collector();
// Initialize all drivers
for (Driver *driver = Driver::getHead(); driver != NULL; driver = driver->getNext()) {
driver->resetCounters();
}
// Set up counters using the associated driver's setup function
for (int i = 0; i < MAX_PERFORMANCE_COUNTERS; i++) {
Counter & counter = gSessionData->mCounters[i];
if (counter.isEnabled()) {
counter.getDriver()->setupCounter(counter);
}
}
// Start up and parse session xml
if (socket) {
// Respond to Streamline requests
StreamlineSetup ss(socket);
} else {
char* xmlString;
xmlString = util->readFromDisk(gSessionData->mSessionXMLPath);
if (xmlString == 0) {
logg->logError(__FILE__, __LINE__, "Unable to read session xml file: %s", gSessionData->mSessionXMLPath);
handleException();
}
gSessionData->parseSessionXML(xmlString);
localCapture = new LocalCapture();
localCapture->createAPCDirectory(gSessionData->mTargetPath);
localCapture->copyImages(gSessionData->mImages);
localCapture->write(xmlString);
sender->createDataFile(gSessionData->mAPCDir);
free(xmlString);
}
// Create user-space buffers, add 5 to the size to account for the 1-byte type and 4-byte length
logg->logMessage("Created %d MB collector buffer with a %d-byte ragged end", gSessionData->mTotalBufferSize, collector->getBufferSize());
collectorFifo = new Fifo(collector->getBufferSize() + 5, gSessionData->mTotalBufferSize*1024*1024, &senderSem);
// Get the initial pointer to the collect buffer
collectBuffer = collectorFifo->start();
// Create a new Block Counter Buffer
buffer = new Buffer(0, 5, gSessionData->mTotalBufferSize*1024*1024, &senderSem);
// Sender thread shall be halted until it is signaled for one shot mode
sem_init(&haltPipeline, 0, gSessionData->mOneShot ? 0 : 2);
// Create the duration, stop, and sender threads
bool thread_creation_success = true;
if (gSessionData->mDuration > 0 && pthread_create(&durationThreadID, NULL, durationThread, NULL)) {
thread_creation_success = false;
} else if (socket && pthread_create(&stopThreadID, NULL, stopThread, NULL)) {
thread_creation_success = false;
} else if (pthread_create(&senderThreadID, NULL, senderThread, NULL)){
thread_creation_success = false;
}
if (gSessionData->hwmon.countersEnabled()) {
if (pthread_create(&countersThreadID, NULL, countersThread, this)) {
thread_creation_success = false;
}
} else {
// Let senderThread know there is no buffer data to send
buffer->setDone();
}
if (!thread_creation_success) {
logg->logError(__FILE__, __LINE__, "Failed to create gator threads");
handleException();
}
// Wait until thread has started
sem_wait(&senderThreadStarted);
// Start profiling
logg->logMessage("********** Profiling started **********");
collector->start();
sem_post(&startProfile);
// Collect Data
do {
// This command will stall until data is received from the driver
bytesCollected = collector->collect(collectBuffer);
// In one shot mode, stop collection once all the buffers are filled
if (gSessionData->mOneShot && gSessionData->mSessionIsActive) {
if (bytesCollected == -1 || collectorFifo->willFill(bytesCollected)) {
logg->logMessage("One shot");
endSession();
}
}
collectBuffer = collectorFifo->write(bytesCollected);
} while (bytesCollected > 0);
logg->logMessage("Exit collect data loop");
if (gSessionData->hwmon.countersEnabled()) {
pthread_join(countersThreadID, NULL);
}
// Wait for the other threads to exit
pthread_join(senderThreadID, NULL);
// Shutting down the connection should break the stop thread which is stalling on the socket recv() function
if (socket) {
logg->logMessage("Waiting on stop thread");
socket->shutdownConnection();
pthread_join(stopThreadID, NULL);
}
// Write the captured xml file
if (gSessionData->mLocalCapture) {
CapturedXML capturedXML;
capturedXML.write(gSessionData->mAPCDir);
}
logg->logMessage("Profiling ended.");
delete buffer;
delete collectorFifo;
delete sender;
delete collector;
delete localCapture;
}