def fetch(filename, nevents, runattrs=None):
    """
    Fetch and save waveform traces from the oscilloscope.

    Args:
        - filename: str
            Filename to store traces in (in hdf5 format).
        - nevents: int
            Number of triggered events to save in `filename`.
    """
    scope = LeCroyScope(setup.scope_ip, timeout=20.0)

    # turn off the display
    scope.send('display off')
    scope.check_last_command()

    # clear the output queue
    scope.clear()

    # get active channels
    channels = scope.getchannels()

    # get scope configuration
    settings = get_settings(scope)

    # get wave descriptors for each channel
    # important to do this before queue is primed!
    # need to trigger in order to get correct wave_array_count
    scope.trigger()

    time.sleep(5.0)

    wavedesc = {}
    for channel in channels:
        wavedesc[channel] = scope.getwavedesc(channel)

    # open up the output file
    f = h5py.File(filename, 'w')

    # set scope configuration
    for command, setting in settings.items():
        f.attrs[command] = setting

    if 'ON' in f.attrs['SEQUENCE']:
        sequence_count = int(f.attrs['SEQUENCE'].split(',')[1])

        if sequence_count < 1:
            raise Exception('sequence count must be a positive number.')
    else:
        sequence_count = 1

    for channel in channels:
        nsamples = wavedesc[channel]['wave_array_count'] // sequence_count

        f.create_dataset('channel%i' % channel, (nevents, nsamples),
                         dtype=wavedesc[channel]['dtype'],
                         chunks=(max(1, min(100, nevents // 100)), nsamples),
                         compression='gzip')

        for key, value in wavedesc[channel].items():
            try:
                f['channel%i' % channel].attrs[key] = value
            except ValueError:
                pass

    if runattrs is not None:
        for name in runattrs:
            for key, value in runattrs[name].items():
                f[name].attrs[key] = value

    # start a timer
    time0 = time.time()

    try:
        i = 0
        while True:
            print '\rsaving event: %i' % i,
            sys.stdout.flush()

            try:
                scope.trigger()
                for channel in channels:
                    wave_array = scope.getwaveform(channel, wavedesc[channel])

                    if sequence_count > 1:
                        try:
                            f['channel%i' % channel][i:i+sequence_count] = \
                                wave_array.reshape(sequence_count, wave_array.size//sequence_count)
                        except ValueError:
                            f['channel%i' % channel][i:i+sequence_count] = \
                                wave_array.reshape(sequence_count, wave_array.size//sequence_count)[:len(f['channel%i' % channel])-i]
                    else:
                        f['channel%i' % channel][i] = wave_array

            except (socket.error, struct.error) as e:
                print '\n' + str(e)
                scope.clear()
                continue

            i += sequence_count

            if i >= nevents:
                print '\rsaving event: %i' % i,
                break

        print

    except KeyboardInterrupt:
        print '\nresizing datasets...'

        for channel in channels:
            f['channel%i' % channel].resize(
                (i, wavedesc[channel]['wave_array_count'] // sequence_count))

        raise

    finally:
        f.close()
        scope.clear()
        scope.send('display on')
        scope.check_last_command()

        elapsed = time.time() - time0

        if i > 0:
            print 'Completed %i events in %.3f seconds.' % (i, elapsed)
            print 'Averaged %.5f seconds per acquisition.' % (elapsed / i)
            print "Wrote to file '%s'." % filename